2 Theme development
The installation chapter is of course a comprehensive guide to installing F-Webshop on your machine. F-Webshop Themes are files that work together to create the design and functionality of a F-webshop site. Each Theme may be different, offering various options for customization and development for site owners.
Required folder and files creation
A new theme folder must be created first in project folder. This is the place where all theme files will be stored and customized.
ProjectName/src/Themes/YourNewThemeName
Starter theme main folder and file structure:
Starter
- config
assets-config.yml
settings.yml
twig-config.yml
socket-config.yml
theme.yml
- Homepage
master.html.twig
index.html.twig
- Resource
- css
style.css.twig
Beside these core files, we will have a lot more files, plugins, layouts and components. All of that highly customizable.
config
Inside the theme folder, it is necessary to create a new folder named config which serves as a place to store main theme configuration files:
- assets-config.yml
- settings.yml
- twig-config.yml
- socket-config.yml
- plugins.yml
- theme.yml
assets-config.yml
This file serves as a list of resources (javascript and stylesheets) which theme uses. These resources are compiled during the project build and they can be organized in any way the developer wants. The example shows the basic skeleton of assets-config.yml which is necessary for compiling to work. Assets for compiling are built under the title (for example - theme_style_generated). First we have the output parameter where we put the path of a compiled style or script from where we will call it in code. Filter depends on CSS (Sass) or JS. Inputs are basically a list of resources to compile.
parameters:
is_secure_server: true
assetic:
assets:
theme_style_generated:
output: 'Resource/FrontendTheme/css/starter_style_compiled.min.css'
filter: f_sass
inputs:
- '../src/Themes/Starter/Resource/css/modals.min.css'
- '../src/Themes/Starter/Resource/css/theme-sass/style.scss'
- '../bower_components/owl.carousel/dist/assets/owl.carousel.css'
- '../bower_components/select2/dist/css/select2.min.css'
- '../bower_components/toastr/toastr.css'
- '../bower_components/jquery.rateit/scripts/rateit.css'
- '../bower_components/fancybox/src/css/*'
- '../src/Themes/Starter/Resource/css/style.css'
- '../src/Themes/Starter/Resource/css/starter.scss'
fontawesome_icons:
output: 'Resource/FrontendTheme/css/fontawesome_icons.min.css'
filter: uglifycss
inputs:
- '../src/Themes/Starter/Resource/css/normalize.css'
- '../bower_components/font-awesome/css/font-awesome.min.css'
theme_scripts_generated:
output: 'Resource/FrontendTheme/js/starter_scripts_compiled.js'
filter: uglifyjs2
inputs:
- '../bower_components/jQuery/dist/jquery.min.js'
- '../src/Themes/Starter/Resource/js/modals.min.js'
- '../global_assets/js/libs/jquery.pubsub.min.js'
- '../global_assets/js/libs/form-serialize.js'
- '../global_assets/js/libs/cropper.min.js'
- '../global_assets/js/empireFinishedImages.js'
- '../bower_components/uikit/js/uikit.min.js'
- '../bower_components/jquery.rateit/scripts/jquery.rateit.min.js'
- '../bower_components/uikit/js/components/pagination.min.js'
- '../web/bundles/bazingajstranslation/js/translator.min.js'
- '../bower_components/owl.carousel/dist/owl.carousel.js'
- '../bower_components/jquery-validation/dist/jquery.validate.js'
- '../bower_components/jquery-validation/src/localization/messages_sr_lat.js'
- '../bower_components/fancybox/src/js/*'
- '../bower_components/dropzone/dist/min/dropzone.min.js'
- '../bower_components/toastr/toastr.min.js'
- '../bower_components/select2/dist/js/select2.js'
- '../src/Themes/Starter/Resource/js/wishlist.js'
- '../global_assets/js/inits.js'
- '../web/bundles/fosjsrouting/js/router.js'
- '../global_assets/js/adapter.js'
- '../global_assets/js/shopping-cart.js'
- '../global_assets/js/currency.js'
- '../global_assets/js/libs/jquery.loadingModal.min.js'
- '../global_assets/js/views.js'
- '../global_assets/js/infiniteScroll.js'
- '../src/Themes/Starter/Resource/js/main.js'
user_register_frontend_compiled_js:
output: 'Resource/FrontendTheme/js/user_register_frontend_compiled.js'
filter: uglifyjs2
inputs:
- '../global_assets/js/user/register.js'
user_password_change_frontend_compiled_js:
output: 'Resource/FrontendTheme/js/user_password_change_frontend_compiled.js'
filter: uglifyjs2
inputs:
- '../global_assets/js/user/change-password.js'
user_password_reset_frontend_compiled_js:
output: 'Resource/FrontendTheme/js/user_password_reset_frontend_compiled.js'
filter: uglifyjs2
inputs:
- '../global_assets/js/user/reset-password.js'
view_ticket_frontend_compiled_js:
output: 'Resource/FrontendTheme/js/view_ticket_compiled.js'
filter: uglifyjs2
inputs:
- '../global_assets/js/user/view-ticket.js'
address_step_checkout_compiled_js:
output: 'Resource/FrontendTheme/js/address_step_checkout_compiled.js'
filter: uglifyjs2
inputs:
- '../global_assets/js/checkout/address-step.js'
checkout_summary_compiled_css:
output: 'Resource/FrontendTheme/js/checkout_summary_compiled.css'
filter: uglifycss
inputs:
- '../src/Themes/FShop/Resource/css/checkout-summary.css'
product_listing_scripts_compiled_js:
output: 'Resource/FrontendTheme/js/product_listing_scripts_compiled.js'
filter: uglifyjs2
inputs:
- '../bower_components/jquery-lib-link/jquery.liblink.js'
- '../bower_components/wnumb/wNumb.js'
- '../global_assets/js/base-pagination.js'
product_listing_pagination_scripts_compiled_js:
output: 'Resource/FrontendTheme/js/product_listing_pagination_scripts_compiled.js'
filter: uglifyjs2
inputs:
- '../global_assets/js/pagination-regular.js'
product_listing_infinite_scroll_scripts_compiled_js:
output: 'Resource/FrontendTheme/js/product_listing_infinite_scroll_scripts_compiled.js'
filter: uglifyjs2
inputs:
- '../global_assets/js/infiniteScrollPagination.js'
elevate_zoom_compiled_js:
output: 'Resource/FrontendTheme/js/elevate_zoom_compiled.js'
filter: uglifyjs2
inputs:
- '../global_assets/js/libs/jquery.elevateZoom.min.js'
#java: /usr/bin/java
node: /usr/bin/nodejs
ruby: null
filters:
uglifycss:
bin: /usr/local/lib/npm/bin/uglifycss
uglifyjs2:
bin: /usr/local/lib/npm/bin/uglifyjs
cssrewrite: ~
sass:
bin: /usr/bin/sass
cache_location: null
style: compressed
- Parameter is_secure_server is recommended to be set to true because of the secure SSL certificate.
is_secure_server: true
- Parameter node should be set to the nodejs path in your development environment which can be checked by using command
which nodejs
- Filters also set paths for uglifycss and uglifyjs in your development environment. Paths for uglifycss and uglifyjs can be checked with command:
which uglifycss
which uglifyjs
settings.yml
This is the core theme file, which imports all other settings that configure the theme:
imports:
- { resource: twig-config.yml }
- { resource: assets-config.yml }
- { resource: services.yml }
- { resource: socket-config.yml }
twig-config.yml
This file configures theme path. It is recommended to name it related to the theme as shown in the example below:
twig:
paths:
"%kernel.root_dir%/../src/Themes/Starter": starterViews
socket-config.yml
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Et sit in, soluta, magni quaerat voluptatum officia iure libero dolorem doloremque quia accusamus, quasi voluptates ullam eos adipisci a earum provident!
plugins.yml
This file generates during project build and sets the settings from the database. These settings can be changed in the Dashboard under Plugin Management. It is important to notice that plugins have their own templates. Default templates are in the core Plugin folder. If the developer needs the core template to be overriden, it should be copied to the theme folder and changed there.
theme.yml
This file stores the initial styling parameters for F-Webshop theme. Theme options are parameters that can be used in style.css.twig as variables and controlled from the database. Inital default values must be set upon theme initialization.
theme:
type: 'frontend'
name: '@starterViews'
theme_options:
name: 'Default'
mainColor: "#ff8c00"
mainColor2: "#cc7000"
secondaryColor: "#7f8c8d"
secondaryColor2: "#95a5a6"
bodyBackgroundColor: "#ffffff"
topnavColor: "#ecf0f1"
topnavTextColor: "#333333"
headerBackgroundColor: "#ffffff"
headerTextColor: "#333333"
menuTextColor: "#ffffff"
footerTextColor: "#333333"
footerColor: "#ecf0f1"
borderRadius: "0px"
font: "Open Sans"
isDefault: true
isCurrent: true
- Parameter type should always be 'frontend'
type: 'frontend'
- Parameter name should be the same as defined previously in twig-config.yml
name: '@starterViews'
- Parameter theme_options serves as an initial theme preset that can later be changed from the database.
theme_options:
name: 'Default'
mainColor: "#ff8c00"
mainColor2: "#cc7000"
secondaryColor: "#7f8c8d"
secondaryColor2: "#95a5a6"
bodyBackgroundColor: "#ffffff"
topnavColor: "#ecf0f1"
topnavTextColor: "#333333"
headerBackgroundColor: "#ffffff"
headerTextColor: "#333333"
menuTextColor: "#ffffff"
footerTextColor: "#333333"
footerColor: "#ecf0f1"
borderRadius: "0px"
font: "Open Sans"
isDefault: true
isCurrent: true
style.css.twig
This file should contain main theme styles. It is important to notice that the styling parameters from theme.yml can be used here as CSS variables and controlled and customized from database. The example for usage of mainColor parameter is shown below:
.main-heading {
color: {{ preset.mainColor|raw }}
}
Creating the homepage
Main file for all website content and the one that is rendered as a homepage of the application is index.html.twig. The recommendation is to use one more template as a basic skeleton of your theme which will load scripts and styles (header and footer). Then you can use index.html.twig to load site inner content. A certain knowledge of Symphony twig is required to work with twig templates.
Creating the base file
The base file for our Starter theme is master.html.twig. It contains the necessary HTML skeleton for the website and other twig included files.
Functions that are used in master.html.twig:
- alligator_seo(): gets SEO data from database
- get_default_favicon(): gets default favicon set in database
master.html.twig
{% set locale = app.request.get('_locale') %}
<!DOCTYPE html >
<html lang="{{ app.request.locale|split('_')[0] }}">
<head>
<meta http-equiv="Content-Type" content="application/xhtml+xml" charset="utf-8">
{% block seo %}
<!-- meta description -->
<title>{{ alligator_seo().seoTitle }}</title>
<meta name="keywords" content="{{ alligator_seo().seoKeywords }}">
<meta name="description" content="{{ alligator_seo().seoDescription }}">
{% endblock seo %}
<meta name="author" content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<!-- social meta data -->
{% block social %}
{% block socialCustom %}
<meta property="og:title" content="{{ alligator_seo().seoTitle }}"/>
<meta property="og:type" content="website" />
<meta property="og:image" content=""/>
<meta property="og:description" content="{{ alligator_seo().seoDescription }}"/>
{% endblock socialCustom %}
<meta property="og:url" content="{{ app.request.uri }}" />
<meta property="og:site_name" content="{{ alligator_seo().seoTitle }}"/>
{% endblock social %}
<!-- favicon -->
<link rel="icon" type="image/x-icon" href="{{ asset(get_default_favicon()) }}" />
<!-- styles -->
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/includes/stylesheets.html.twig' %}
{% block stylesheets %}
{% endblock %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'html-head-end-scripts'} %}
{% block headBlock %}
{% endblock %}
</head>
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/includes/global-variables.html.twig' %}
<body class="starter-theme">
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/includes/scripts.html.twig' %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'plugins-scripts'} %}
{% block body %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/templates/header-layout.html.twig' %}
<div class="starter-content clearfix">
{% block content %}
{% endblock %}
</div>
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/templates/footer-layout.html.twig' %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/templates/product-remove-from-cart-modal.html.twig' %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/templates/product-add-to-cart-confirm-modal.html.twig' %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/templates/remove-all-products-from-cart-modal.html.twig' %}
{% endblock %}
{% block scripts %}
{% endblock %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/templates/login-modal.html.twig' %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'html-body-end-scripts'} %}
</body>
</html>
The default HTML language is based on the default language which can be set in the database. app.request.locale prints this language locale.
<html lang="{{ app.request.locale|split('_')[0] }}">
Twig blocks seo and social use data from database. For example alligator_seo().seoDescription will print out SEO description which can be edited in General settings on the database.
Users can add the default favicon in the database also, and it will be displayed on our website via get_default_favicon():
<link rel="icon" type="image/x-icon" href="{{ asset(get_default_favicon()) }}" />
Now, a twig called stylesheets is included. This twig is placed in the includes directory of the theme. It is used for importing theme styles on our website. The example shows how our compiled styles are included in stylesheets.twig:
stylesheets.html.twig
<link rel="stylesheet" href="{{ asset('Resource/FrontendTheme/css/fontawesome_icons.min.css') }}"/>
<link rel="stylesheet" href="{{ asset('Resource/FrontendTheme/css/starter_style_compiled.min.css') }}"/>
Next twig that we include is global-variables. It is basically a set of variables that are commonly used accross our website.
Function that is used:
- get_current_user_settings: gets current user settings from the database
global-variables.html.twig
{# Set glaobal locale parameter #}
{% set locale = app.request.get('_locale') %}
{% set user = get_logged_user() %}
{# @var userSettings \Alligator\Model\SettingsBundle\Entity\UserSettings #}
{% set userSettings = get_current_user_settings() %}
{% set currentRoute = app.request.get('_route') %}
{# Global javascript variables #}
<script>
var predefinedNumOfCategories = {{ alligator_store_info().numberOfCategoriesToShow ? alligator_store_info().numberOfCategoriesToShow : 10 }};
var LOCALE_GLOBAL = '{{ app.request.getLocale }}';
{% if get_logged_user() %}
const USER_LOGGED = true;
{% else %}
const USER_LOGGED = false;
{% endif %}
var currentRoute = "{{ app.request.attributes.get('_route') }}";
var currentLocale = "{{ app.request.attributes.get('_locale') }}";
var currentCurrency = "{{ app.session.get('current_currency') }}";
var currentCurrencySymbol = "{{ getCurrencySymbol(app.session.get('current_currency')) }}";
var allAddToCartButtons = [];
var defaultPorductImageUrl = "{{ asset('Resource/FrontendTheme/images/default_product.png') }}";
var defaultDecimalSeparator = "{{ userSettings.decimalSeparator }}";
var defaultThousandSeparator = "{{ userSettings.thousandsSeparator }}";
var defaultDecimalPlace = "{{ userSettings.decimalPlaces }}";
var schemaHost = window.location.protocol + "//" + window.location.host;
</script>
Variables available:
- locale: default language set in Dashboard
- user: logged user data
- userSettings: current user settings
- predefinedNumOfCategories: predefined number of categories that is set in Dashboard
- currentRoute: current website route
- LOCALE_GLOBAL: default language set in Dashboard
- currentLocale: default language set in Dashboard
- currentCurrency: active currency (also set in Dashboard)
- currentCurrencySymbol: currency symbol
- allAddToCartButtons: all Add to Cart buttons
- defaultPorductImageUrl: default product image for cases when no images are set
- defaultDecimalSeparator: default decimal separator for prices
- defaultThousandSeparator: default thousand separator for prices
- defaultDecimalPlace: default decimal place for prices
Now we are including the scripts for our website via scripts.html.twig. This twig is placed in the includes directory of the theme. It is used for importing theme scripts on our website. The example shows how our compiled scripts are included in scripts.html.twig along with translation and routing scripts which are required also:
scripts.html.twig
<script src="{{ asset('Resource/FrontendTheme/js/starter_scripts_compiled.js') }}"></script>
<script defer src="{{ path('bazinga_jstranslation_js') }}?locales={{ app.request.locale }}"></script>
<script defer src="{{ path('fos_js_routing_js', {"callback": "fos.Router.setData"}) }}"></script>
The twig include shown below explains the way to set a layout for importing blocks from the Dashboard. This layout is used to import additional plugin scripts. It is important to provide layout_name which can be accessed form the Dashboard while creating a new block.
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'plugins-scripts'} %}
Block body is the main block of our theme. This is where all the content is placed.
{% block body %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/templates/header-layout.html.twig' %}
<div class="site-content">
{% block content %}
{% endblock %}
</div>
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/templates/footer-layout.html.twig' %}
{% endblock %}
First, header-layout is included. This is where the top part of the website is rendering. It includes store information, logo, categories and custom menu. First, megamenu layout is included. Megamenu Plugin is loaded in this layout, and the template is within Plugins/EmpireMegamenuPlugin/views folder - megamenu.html.twig. Post header block is loaded after megamenu, that's the layout for additional plugins if required.
header-layout.html.twig
<header class="starter-header clearfix">
{% block header %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'megamenu'} %}
{% endblock %}
{% block postHeader %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'post-header-block'} %}
{% endblock %}
</header>
Block content is basically the body of our website. More on that will be covered with index.html.twig documentation.
Footer layout twig includes a newsletter twig, basic store information form the Dashboard and active plugin links as shown in the template below:
Function that is used:
- alligator_store_info(): retrieves store information from database
footer-layout.html.twig
{% set storeInfo = alligator_store_info() %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'newsletter'} %}
{% set storeInfo = alligator_store_info() %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'newsletter'} %}
<footer class="starter-footer clearfix">
<div class="container">
<div class="flex-align">
<ul>
<li>{{ storeInfo.slogan }}</li>
<li><a href="tel:{{ storeInfo.storePhone }}">{{ storeInfo.storePhone }}</a></li>
<li><a href="mailto:{{ storeInfo.storeEmail }}">{{ storeInfo.storeEmail }}</a></li>
<li>{{ storeInfo.storeAddress }}</li>
</ul>
<ul>
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'footer-menu-plugin-links'} %}
</ul>
</div>
</div>
</footer>
Next block is scripts. It's a block where additional scripts can be added in child templates. After that, a login-modal.html.twig is included from the templates directory where it can be edited as required. And finally we have html-body-end-scripts layout which is used for additional scripts before body closing. These scripts can be loaded through Block elements from the Dashboard.
{% block scripts %}
{% endblock %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/templates/login-modal.html.twig' %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'html-body-end-scripts'} %}
Login modal uses Bootstrap modal classes, and the template is within templates directory:
login-modal.html.twig
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="loginModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="modal-title">{% trans %}Login{% endtrans %}
<span aria-hidden="true" class="close" data-dismiss="modal" aria-label="Close"></span>
</h4>
</div>
<div class="modal-body">
<form role="form" action="{{ path('fos_user_security_check') }}" method="post" id="login-form-modal" class="login-modal-content">
<input type="hidden" name="_csrf_token" value="{{ fos_csrf_provider.getToken('authenticate') }}" />
<input type="hidden" value="{{ app.request.getUri }}" name="_route" >
<span id="loginMessage"></span>
<div class="form-group">
<label for="username">{% trans %}Username{% endtrans %}</label>
<input type="text" id="username" name="_username" class="form-control" value="" required="required">
</div>
<div class="form-group">
<label for="password">{% trans %}Password{% endtrans %}</label>
<input type="password" id="password" name="_password" class="form-control" required="required">
</div>
<div class="remember-forgot">
<label for="remember-me">
<input type="checkbox" id="remember-me">
<span>{% trans %}Remember me{% endtrans %}</span>
</label>
</div>
<div class="block-forgot-pass">
{% trans %}Forgot password?{% endtrans %}
<a href="{{ path('fos_user_resetting_request') }}" id="forgot-pass-modal">
{% trans %}Click here{% endtrans %}
</a>
</div>
</form>
</div>
<div class="modal-footer">
<div class="form-group">
<a tabindex="0" href="{{ path('register') }}" title="{% trans %}Register{% endtrans %}">{% trans %} REGISTER {% endtrans %}</a>
<button class="btn btn-primary" tabindex="0" type="submit" id="_submit" name="_submit">{% trans %}Login{% endtrans %}</button>
</div>
</div>
</div>
</div>
</div>
Header
Functions that are used:
- alligator_store_info(): retrieves store information from database
- alligator_all_currencies(): gets all defined currencies
- get_logged_user(): checks if user is logged in
- get_backend_route_for_user(): sends user to the defined route on the Dashboard based on his role
- get_search_settings_form_cache(): gets search settings defined in the Dashboard
- tree_categories(): lists product categories
- get_tree_menu(): lists menu items as defined in Ember Menu Plugin on the Dashboard
Megamenu template is the main template twig for header section of the website. Starter theme uses the example below. First we set variables for storeInfo and currencies.
After that, we have a few crucial links:
- Login
- Register
- Dashboard
- Profile (if user is logged in)
- Logout (if user is logged in)
- Compare
Also, we have language and currency dropdowns. Language is included as separate twig which is loaded in the language layout. The template for language can be changed in Themes/Starter/Plugins/EmpireLanguagePlugin/views/language.html.twig.
Function that is used in this template:
- alligator_available_languages(): lists all available languages
language.html.twig
{% set currentPath = app.request.attributes.get('_route') %}
{% set displayLanguageDropdown = alligator_available_languages()|length > 1 ? true : false %}
{% block languageBlock %}
{% if currentPath == 'fos_user_resetting_reset' %}
<li class="dropdown switcher switcher-language">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button">
{{ app.request.getLocale|upper }}
{% if displayLanguageDropdown %}
<span class="caret"></span>
{% endif %}
</a>
{% if displayLanguageDropdown %}
<ul class="dropdown-menu language language-toggle">
{% for language in alligator_available_languages() %}
<li><a id='{{ language.locale }}' href="{{ path(currentPath, {'_locale': language.locale, token: app.request.get('token')}) }}">{{ language.name|trans }}</a></li>
{% endfor %}
</ul>
{% endif %}
</li>
{% else %}
<li class="switcher switcher-language dropdown">
<a href="#" class="language-dropdown dropdown-toggle" data-toggle="dropdown">
<span>{{ app.request.getLocale|upper }}</span>
{% if displayLanguageDropdown %}
<span class="caret"></span>
{% endif %}
</a>
{% if displayLanguageDropdown %}
<ul role="menu" class="language language-toggle dropdown-menu">
{% for language in alligator_available_languages() %}
<li>
<a class="linkable" data-locale="{{ language.locale }}" id='{{ language.locale }}' href="#">{{ language.name|trans }}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endif %}
{% endblock %}
Logo image is shown if it's defined in the Dashboard:
{% if alligator_store_info().baseLogoUrl is not null %}
<a href="{{ path('permalink_frontend_route') }}">
<img src="{{ alligator_store_info().baseLogoUrl }}" alt="logo"/>
</a>
{% endif %}
After the logo we are checking which type of search is defined in the Dashboard, and depending on that we are including a search twig template.
{% set searchSettings = get_search_settings_form_cache() %}
{% if searchSettings.ajaxSearch %}
{% include [constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireMegamenuPlugin/views/search.html.twig', '@megamenuPluginViews/search.html.twig'] %}
{% else %}
{% include [constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireMegamenuPlugin/views/search-without-categories.html.twig', '@megamenuPluginViews/search-wihout-categories.html.twig'] %}
{% endif %}
If the Ajax search setting is chosen, we are using search.html.twig template from the Megamenu plugin.
Function that is used:
- get_default_loader(): gets default loader source image which can be set in Dashboard
search.html.twig
<form action="{{ path('products_search_front', {"_locale": locale}) }}" autocomplete="off">
<input id="search-input" type="text" name="query" placeholder="{% trans %}Search entire store here...{% endtrans %}" class="form-control"/>
<input id="search-input-category" type="text" name="category" placeholder="{% trans %}Search entire store here...{% endtrans %}" class="hidden" />
<button class="btn btn-search btn-primary" type="button" id="search-button-icon"><span>{% trans %}Search{% endtrans %}</span></button>
</form>
<div id="searchResult">
<div class="loader"><img src="{{ asset(get_default_loader()) }}" ></div>
<div class="ajax-search-wrapper">
<h4>{% trans %}Search results in {% endtrans %} <span id="search-res-category">{% trans %}All categories{% endtrans %}</span></h4>
<h4><button class="btn btn-primary" id="show-all-results">{% trans %}View all results{% endtrans %}</button></h4>
<ul class="clearfix">
</ul>
</div>
</div>
If the classic search setting with categories is chosen, we are using search-without-categories.html.twig.
Function that is used:
- get_default_loader(): gets default loader source image which can be set in Dashboard
search-without-categories.html.twig
{% set menuSearchCats = getAllCats() %}
<div class="main-search-form">
<form action="{{ path('products_search_front', {"_locale": locale}) }}" autocomplete="off" method="get" role="form" enctype="multipart/form-data">
<input id="search-input" type="text" name="query" placeholder="{% trans %}Search{% endtrans %}" class="form-control"/>
<input id="search-input-category" type="text" name="category" placeholder="{% trans %}Search{% endtrans %}" class="hidden" />
<button type="button" id="search-button-icon"><span>{% trans %}Search{% endtrans %}</span></button>
</form>
<div id="searchResult">
<div class="loader"><img src="{{ asset(get_default_loader()) }}" ></div>
<h4>{% trans %}Search results in {% endtrans %} <span id="search-res-category">{% trans %}All categories{% endtrans %}</span></h4>
<ul class="clearfix">
</ul>
</div>
</div>
In the next section we can see the default block for shopping cart with the link to the checkout summary and also a div with the id quickCart which receives products that are added to cart through AJAX.
<a href="{{ path('checkout_summary') }}" role="button">
<span id="shopping-cart-number-span-sticky">0</span>
<span>{% trans %}Item(s){% endtrans %} - </span>
<span>0</span>
</a>
<form>
<div id="quickCart">
</div>
</form>
megamenu.html.twig
{% set storeInfo = alligator_store_info() %}
{% block menu %}
<div class="wrapper-fluid">
<div class="top-navigation">
{% set currencies = alligator_all_currencies() %}
<ul>
<li><span>{% trans %}Contact us{% endtrans %}:</span> {{ storeInfo.storePhone }}</li>
<li><span>{% trans %}Email{% endtrans %}:</span> {{ storeInfo.storeEmail }}</li>
</ul>
<ul>
{% if not get_logged_user() %}
<li>
{% if alligator_store_info().loginModal is not null %}
<a {% if alligator_store_info().loginModal == 1 %} data-toggle="modal" href="#myModal"{% else %} href="{{ path('fos_user_security_login') }}" {% endif %} title="{% trans %}Login{% endtrans %}" class="nav-menu-icons">
{% trans %} Login {% endtrans %}
</a>
{% endif %}
{% trans %}or{% endtrans %}
<a href="{{ path('register') }}" title="{% trans %}Register{% endtrans %}">
{% trans %} Register {% endtrans %}
</a>
</li>
{% endif %}
{% if get_backend_route_for_user() %}
<li>
<a href="{{ path(get_backend_route_for_user(), {'_locale':locale}) }}" title="{% trans %}Admin dashboard{% endtrans %}">
{% trans %}Dashboard{% endtrans %}
</a>
</li>
{% endif %}
{% if get_logged_user() %}
<li>
<a href="{{ path('user_profile', {'_locale':locale}) }}" title="{% trans %}Profile{% endtrans %}" >
{% trans %} Profile {% endtrans %}
</a>
</li>
<li>
<a href="{{ path('fos_user_security_logout') }}" id="logout-btn" title="{% trans %}Logout{% endtrans %}" >
{% trans %} Logout {% endtrans %}
</a>
</li>
{% endif %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'language'} %}
{% if currencies|length >0 %}
<li class="dropdown" id="currency">
<a data-toggle="dropdown" role="button" href="javascript:;" class="dropdown-toggle">
<span>{{ app.request.session.get('current_currency_code') }}</span>
{% if currencies|length > 1 %}
<span class="caret"></span>
{% endif %}
</a>
{% if currencies|length > 1%}
<ul class="currency dropdown-menu dropdown-menu-right">
{% for currency in currencies %}
<li><a href="" id="{{ currency.name }}">{{ currency.symbol }}</a></li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endif %}
<li>
<a href="{{ path('products_compare_front') }}">
{% trans %}Compare{% endtrans %}
</a>
</li>
</ul>
</div>
</div>
<nav class="navbar navbar-default">
<div class="wrapper">
<div class="flex-align">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#menu-collapse" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
{% if alligator_store_info().baseLogoUrl is not null %}
<a class="navbar-brand" href="{{ path('permalink_frontend_route') }}">
<img src="{{ alligator_store_info().baseLogoUrl }}" alt="logo" class="img-responsive"/>
</a>
{% endif %}
</div>
<div class="collapse navbar-collapse" id="menu-collapse">
{{ get_tree_menu(true, locale)|raw }}
<ul class="nav navbar-nav">
{% set searchSettings = get_search_settings_form_cache() %}
{% if searchSettings.ajaxSearch %}
<li>{% include [constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireMegamenuPlugin/views/search.html.twig', '@megamenuPluginViews/search.html.twig'] %}</li>
{% else %}
<li>{% include [constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireMegamenuPlugin/views/search-without-categories.html.twig', '@megamenuPluginViews/search-wihout-categories.html.twig'] %}</li>
{% endif %}
<li class="block-minicart dropdown">
<a class="dropdown-toggle shopping-cart shopping-cart-number cart-btn" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false" href="{{ path('checkout_summary') }}">
<i class="fa fa-shopping-cart"></i>
<span id="shopping-cart-number-span-sticky">0</span>
{% trans %}Item(s){% endtrans %}
</a>
<div class="dropdown-menu cart-toggle">
<form>
<div class="minicart-content-wrapper quickCart" id="quickCart">
</div>
</form>
</div>
</li>
</ul>
</div>
</div>
{{ tree_categories()|raw}}
</div>
</nav>
{% endblock %}
{% block megaMenuScripts %}
{% include '@starterViews/Plugins/EmpireMegamenuPlugin/views/megamenu-scripts.html.twig' %}
{% endblock %}
Content
All content between header and footer is going through the twig block content in index.html.twig.
Function that is used is:
- get_slider_settings(): gets slider settings from the database
First line of the index.html.twig shows us that it is the child of master.html.twig:
{% extends constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/master.html.twig' %}
Then we have a headBlock which sets a layout called homepage-head-scripts where additional scripts can be loaded on top of content. For example, by default Google rich snippet is loaded in this layout.
{% block headBlock %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'homepage-head-scripts'} %}
{% endblock %}
Now comes the twig block called content, on top of which we load all required modals by setting a layout modals-all-include-scripts.
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'modals-all-include-scripts'} %}
Modals
The default required modals are:
- add-to-compare-modal.html.twig
- add-new-compare-list.html.twig
- multiple-wishlists-modal.html.twig
- multiple-wishlists-add-modal.html.twig
First modal is add-to-compare modal, which serves as a block for adding products to compare list.
Function that is used:
- alligator_get_compare_lists(): gets all available compare lists from the database
This modal lists available compare lists if there are any, and enables user to choose where to add product for compare.
add-to-compare-modal.html.twig
{% set compareLists = alligator_get_compare_lists() %}
{% set compareListsIDs = [] %}
<div class="modal fade" id="compareModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="myModalLabel">{% trans %}Choose compare list{% endtrans %}
<span class="close" data-dismiss="modal" aria-label="Close">x</span>
</h4>
</div>
<div class="modal-body">
{% if compareLists is iterable %}
{% if compareLists|length == 0 %}
<p id="noCompareLists">{% trans %}Please make some compare lists first{% endtrans %}</p>
<div id="listOfCompareLists"></div>
{% else %}
<div class="listOfCompareListsScroll">
<div id="listOfCompareLists">
{% for compareList in compareLists %}
{% set compareListsIDs = compareListsIDs|merge([compareList.id]) %}
<button class="addToCompareList" data-list-id="{{ compareList.id }}"> {{ compareList.name }}</button>
{% endfor %}
</div>
</div>
{% endif %}
<div class="modal-footer">
<a class="btn btn-primary" type="button" data-toggle="modal" data-target="#compare-list-add-modal">{% trans %}Add compare list{% endtrans %}</a>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireProductComparePlugin/views/add-new-compare-list.html.twig' %}
<script src="{{ asset('Resource/Plugins/EmpireProductComparePlugin/product-compare_compiled.js') }}"></script>
<script>
/** init add to compare list buttons */
var compareListsIDs = {{ compareListsIDs|json_encode|raw }};
$('.addToCompareList').each(function(index){
var multipleCompareListsButton = new MultipleCompareListsButton($(this)[0], compareListsIDs[index]);
});
</script>
User can also create a new compare list through add-new-compare-list modal.
add-new-compare-list-modal.html.twig
{% block addNewCompareList %}
<div id="compare-list-add-modal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{% trans %}Add compare list{% endtrans %}
<span class="close" data-dismiss="modal" aria-label="Close">x</span>
</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="compareListTitle">{% trans %}Compare list name{% endtrans %}</label>
<input type="text" class="form-control" name="compareListTitle" id="compareListTitle"><br>
</div>
<div class="modal-footer">
<button class="btn btn-primary" type="button" id="add-compare-list-form-btn">{% trans %}Add{% endtrans %}</button>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
Next modal is multiple-wishlists-modal.html.twig which lists available wishlists from the database for logged in user, or prompts a login form for guest users.
Function that is used:
- alligator_get_wishlists_modal(): lists all available wihlists from the database for current user
multiple-wishlists-modal.html.twig
{% block multipleWishListMain%}
{% set wishlists = alligator_get_wishlists_modal() %}
{% set wishlistIds = [] %}
<!-- Modal where user selects list to add item to-->
<div class="modal fade" id="multipleWishlistsModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="myModalLabel">{% trans %}Choose wishlist{% endtrans %}
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">x</span>
</button>
</h4>
</div>
<div class="modal-body">
{% if wishlists is iterable %}
{% if wishlists|length == 0 %}
<p class="text-center" id="noWishlists">{% trans %}Please make some wishlists first{% endtrans %}</p>
{% endif %}
<div id="listOfWishlists" class="form-group" {{ wishlists|length == 0 ? ' style="display:none;" ' }}>
<h3><span>{% trans %}Select wish list for this product{% endtrans %}</span></h3>
<div class="listOfWishlistsScroll">
{% for wishlist in wishlists %}
{% set wishlistIds = wishlistIds|merge([wishlist.id]) %}
<a href="#" class="addToWishlist"
data-list-id="{{ wishlist.id }}">
{{ wishlist.name }}
</a>
{% endfor %}
</div>
</div>
<div class="modal-footer">
<a class="user-wishlist-btn" data-toggle="modal" data-target="#wishlist-add-modal">{% trans %}Add wishlist{% endtrans %}</a>
</div>
{% elseif wishlists == false %}
<p>{% trans %}Log in to add to wishlist{% endtrans %}</p>
<form role="form" action="{{ path('fos_user_security_check') }}" method="post" id="login-form-modal">
<input type="hidden" name="_csrf_token" value="{{ fos_csrf_provider.getToken('authenticate') }}" />
<div class="form-group">
<label for="_username">{% trans %}Username{% endtrans %}:</label>
<input type="text" id="username" name="_username" class="form-control" value="" required="required">
</div>
<div class="form-group">
<label for="">{% trans %}Password{% endtrans %}:</label>
<input type="password" id="password" name="_password" class="form-control" required="required">
</div>
<label for="remember_me">
<input type="checkbox" name="remember_me" id="remember_me">
<span>{% trans %}Remember me{% endtrans %}</span>
</label>
<a href="{{ path('fos_user_resetting_request') }}" id="forgot-pass-modal">
<span>{% trans %}Forgot password?{% endtrans %}</span>
<span class="click">{% trans %}Click here{% endtrans %}</span>
</a>
<div class="modal-footer">
<div class="form-group login-multiple-options">
{% block socialNetworkLogInBlock %}
{% set homepageUrlParam = url('homepage') %}
<a data-toggle="tooltip" title="{% trans %}Log in with{% endtrans %} Facebook" data-placement="bottom" href="{{ path('hwi_oauth_service_redirect', {service: "facebook", homepage_url: homepageUrlParam}) }}" class="btn-facebook">
<i class="fa fa-facebook-official"></i>
</a>
<a data-toggle="tooltip" title="{% trans %}Log in with{% endtrans %} Google+" data-placement="bottom" href="{{ path('hwi_oauth_service_redirect', {service: "google", homepage_url: homepageUrlParam}) }}" class="btn-google">
<i class="fa fa-google-plus-square" aria-hidden="true"></i>
</a>
<a data-toggle="tooltip" title="{% trans %}Log in with{% endtrans %} Twitter" data-placement="bottom" href="{{ path('hwi_oauth_service_redirect', {service: "twitter", homepage_url: homepageUrlParam}) }}" class="btn-twitter">
<i class="fa fa-twitter-square" aria-hidden="true"></i>
</a>
{% endblock socialNetworkLogInBlock %}
<button tabindex="0" type="submit" id="_submit" name="_submit">{% trans %}LOG IN{% endtrans %}</button>
<a href="{{ path('register') }}" title="{% trans %}Register{% endtrans %}">{% trans %} Register {% endtrans %}</a>
</div>
</div>
</form>
{% endif %}
</div>
</div>
</div>
</div>
{% block multipleWishlistContent %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireWishlistsPlugin/views/multiple-wishlists-add-modal.html.twig' %}
{% endblock multipleWishlistContent %}
{% block multipleWIshlIstJSLogic %}
<script src="{{ asset('Resource/Plugins/EmpireWishlistsPlugin/js/multiple_wishilst_frontend_compiled.js') }}"></script>
<script>
/** init add to wishlist buttons */
var wishlistIds = {{ wishlistIds|json_encode|raw }};
$('.addToWishlist').each(function(index){
var multipleWishListsButton = new MultipleWishListsButton($(this)[0], wishlistIds[index]);
});
</script>
{% endblock multipleWIshlIstJSLogic %}
{% endblock %}
Last modal is multiple-wishlists-add-modal.html.twig where user can add new wishlist.
multiple-wishlists-add-modal.html.twig
<div id="wishlist-add-modal" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{% trans %}Add wishlist{% endtrans %}
<span class="close" data-dismiss="modal" aria-label="Close">x</span>
</h4>
</div>
<div class="modal-body">
<div class="form-group">
<label for="wishlistTitle">{% trans %}Wishlist name{% endtrans %}</label>
<input type="text" class="form-control" name="wishlistTitle" id="wishlistTitle"><br>
</div>
<button type="button" id="add-wishlist-form-btn">{% trans %}Add{% endtrans %}</button>
</div>
</div>
</div>
</div>
Slider
Now we are checking slider settings from the database, to see if we will display a full width slider or a 70% slider with a sidebar of 30%. In the first case, we load a slider-content.html.twig which is loaded into slider layout. And in second case we are loading categories from the database first and then the slider-content.html.twig.
{% set sliderSettings = get_slider_settings() %}
{% if sliderSettings.fullWidth %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'slider'} %}
{% else %}
{{ tree_categories()|raw}}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'slider'} %}
{% endif %}
slider.html.twig is loaded directly from the plugin and it sets a layout for slider-content.html.twig which loads the images along with the settings from the database. This is the default view:
slider-content.html.twig
{% block sliderMain %}
{% block sliderContent %}
{% set autoPlay = sliderSettings.autoPlay|json_encode == 0 ? false : true %}
{% if autoPlay %}
{% set autoPlaySpeed = sliderSettings.autoPlay %}
{% endif %}
{% set videoFormats = ['mp4', 'avi'] %}
<div class="owl-carousel homepage-carousel"
data-nav="{{ sliderSettings.navigation|json_encode }}"
data-dots="{{ sliderSettings.pagination|json_encode }}"
data-margin="0"
data-items='1'
data-autoplayTimeout="{{ sliderSettings.autoPlay|json_encode }}"
data-autoplay="{{ autoPlay }}"
data-animateIn="{{ sliderSettings.transitionStyle }}"
data-animateOut="{{ sliderSettings.transitionStyle|replace({'In': "Out"}) }}"
data-loop="{{ slides|length > 1 ? 'true' : 'false' }}">
{% for slide in slides %}
{% if not slide.baseImageUrl and slide.slideLink %}
<div data-slide-title="{{ slide.slideName }}"><a class="owl-video" href="{{ slide.slideLink }}"></a></div>
{% elseif slide.baseImageUrl|split('.')[slide.baseImageUrl|split('.')|length-1] in videoFormats %}
<div data-slide-title="{{ slide.slideName }}">
<a class="owl-video" href="{{ slide.baseImageUrl }}"></a>
</div>
{% else %}
<div class="item item{{ loop.index }}" data-slide-title="{{ slide.slideName }}" style="background-image:url('{{ slide.baseImageUrl }}');">
{% if slide.linkButton %}
<div class="{{ slide.logoPosition ? slide.logoPosition }}">
{% if slide.logoImageUrl %}
<img class="lazyOwl" src="{{ slide.logoImageUrl }}" alt="{{ slide.slideName }}"/>
{% endif %}
</div>
<div class="{{ slide.textPosition ? slide.textPosition }}">
{% set newTab = true %}
{% if httpHost in slide.slideLink %}
{% set newTab = false %}
{% endif %}
<span {{ slide.textColor ? ('style="color: ' ~ slide.textColor ~';"')|raw }}>{{ slide.slideName }}</span>
<span {{ slide.textColor ? ('style="color: ' ~ slide.textColor ~';"')|raw }}>{{ slide.slideHTML|raw }}</span>
{% if slide.linkButton %}
<a href="{{ slide.slideLink }}" {% if newTab %} target="_blank" {% endif %}>{% trans %}shop now{% endtrans %}</a>
{% endif %}
</div>
{% else %}
<a target="_blank" href="{{ slide.slideLink|raw }}">
<div class="{{ slide.logoPosition ? slide.logoPosition }}">
{% if slide.logoImageUrl %}
<img class="lazyOwl" src="{{ slide.logoImageUrl }}" alt="{{ slide.slideName }}"/>
{% endif %}
</div>
<div class="{{ slide.textPosition ? slide.textPosition }}">
{% set newTab = true %}
{% if httpHost in slide.slideLink %}
{% set newTab = false %}
{% endif %}
<span {{ slide.textColor ? ('style="color: ' ~ slide.textColor ~';"')|raw }}>{{ slide.slideName }}</span>
<span {{ slide.textColor ? ('style="color: ' ~ slide.textColor ~';"')|raw }}>{{ slide.slideHTML|raw }}</span>
{% if slide.linkButton %}
<a href="{{ slide.slideLink }}" {% if newTab %} target="_blank" {% endif %} >{% trans %}shop now{% endtrans %}</a>
{% endif %}
</div>
</a>
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
{% endblock %}
{% endblock %}
Main content and sidebar
Function that is used:
- count_plugins_for_layout(): counts available plugins for desired layout
In this part of index.html.twig, we are first checking if there are any available plugins for sidebar layout. If that is the case, we will display a 9-3 layout with sidebar and main content. If there are no plugins, we display a full width main content without sidebar. User can control plugins and blocks for this layout on the Dashboard Layout Manager or Page Manager.
<div class="main-content">
{% set sidebar = count_plugins_for_layout('sidebar-layout') %}
{% if sidebar %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/layout-9-3.html.twig' %}
{% else %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/layout-12.html.twig' %}
{% endif %}
</div>
Now we have two default twig templates for these layouts. It is important to notice that this is highly customizable and that all kind of changes are possible when it comes to organizing content. First we have 9-3 layout which first sets a layout for sidebanners which can be added throug Block elements plugin. Below that the layouts for sidebar and main content are set so that users can organize content from the Layout or Pages Manager.
layout-9-3.html.twig
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'sidebanners-layout'} %}
{% block aside %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'sidebar-layout'} %}
{% endblock %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'main-content'} %}
Also, we have a full width (12) layout which first sets a layout for sidebanners which can be added throug Block elements plugin. Below that the layouts for main content is set so that users can organize content from the Layout or Pages Manager.
layout-12.html.twig
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'sidebanners-layout'} %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'main-content'} %}
Right before footer, we have one more layout available and that is the full width layout where users can also organize content from the Layout or Pages manager.
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'full-width'} %}
Full index.html.twig looks like this:
index.html.twig
{% extends constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/master.html.twig' %}
{% block stylesheets %}
{% if preset is defined %}
<style>
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Resource/css/style.css.twig' %}
</style>
{% endif %}
{% endblock %}
{% block headBlock %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'homepage-head-scripts'} %}
{% endblock %}
{% block content %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'modals-all-include-scripts'} %}
{% set sliderSettings = get_slider_settings() %}
{% if sliderSettings.fullWidth %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'slider'} %}
{% else %}
{{ tree_categories()|raw}}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'slider'} %}
{% endif %}
<div class="main-content">
{% set sidebar = count_plugins_for_layout('sidebar-layout') %}
{% if sidebar %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/templates/layout-9-3.html.twig' %}
{% else %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/templates/layout-12.html.twig' %}
{% endif %}
</div>
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'full-width'} %}
{% block scripts %}
{% endblock %}
{% endblock %}
Quick cart
The quick cart option is enabled by default, and the main template for it is quick-cart.html.twig. It fills the quick cart div with newely added products via AJAX.
Functions that are used:
- getDefaultExtension(): gets the default extension for images from the database
- alligator_json_convert(): converts prices to the current currency
The default template looks like this:
quick-cart.html.twig
{% block quickCartContent %}
{% if order is not null %}
{% set locale = app.request.get('_locale') %}
{% set orderNumber = order.items | length %}
<div class="minicart-items-wrapper">
<ol class="minicart-items qc-product-wrap">
{% for item in order.items if item.product != null %}
<li class="clearfix">
<a href="#" title="The Name Product">
{% if item.productAttValues|length and item.productAttValues[0].baseImageUrl %}
<img src=" {{ item.productAttValues[0].baseImageUrl }}" class="img img-responsive" alt="">
{% elseif item.product.baseImageUrl %}
<img src=" {{ getDefaultExtension(item.product.baseImageUrl) }}" class="img img-responsive" alt="">
{% else %}
<img src="{{ asset('Resource/FrontendTheme/images/default_product.png') }}" alt="">
{% endif %}
</a>
<div>
<a href="{{ path('product_front_slug',{'_locale': locale, 'slug': item.product.slug}) }}">{{ ('product.' ~ item.product.id ~ '.name') | trans({}, 'product-entities') }}</a>
<div>
<span>{{ item.quantity }}</span>
<p>x</p>
<span> {{ item.unitPrice | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</span>
</div>
<span> {{ (item.quantity * item.unitPrice) | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</span>
<a class="action delete remove_item_{{ item.id }}" href="#" id="remove_item_{{ item.id }}">
<i class="fa fa-close"></i>
</a>
</div>
</li>
<script>
var item = {
unit_price: {{ item.unitPrice ? item.unitPrice : '0' }},
product: {
id: {{ item.product.id }},
simple_product: {
number_in_stock: {{ item.product.simpleProduct.numberInStock }}
},
quantity_step: {{ item.product.quantityStep ? item.product.quantityStep : 1 }}
},
quantity: {{ item.quantity }},
product_att_values: [
{% for value in item.productAttValues %}
{
id: {{ value.id }},
cost: {{ get_price(value) }},
sku: '{{ value.sku }}',
quantity: {{ value.quantity }},
min_quantity: {{ value.minQuantity }},
quantity_step: {{ value.quantityStep }},
display_to_user: {{ value.displayToUser ? "true" : "false" }},
price_on_action: {{ get_action_price(value) }},
attribute_values: [
{% for attr in value.attributeValues %}
{
id: {{ attr.id }}
}
{% if not loop.last %},{% endif %}
{% endfor %}
]
}
{% if not loop.last %},{% endif %}
{% endfor %}
]
};
$('.remove_item_'+{{ item.id }}).each(function(index, element) {
var removeBtn = new RemoveButton($(element)[0], item);
});
</script>
{% else %}
<p>{% trans %}No products added{% endtrans %}</p>
{% endfor %}
</ol>
</div>
{% if order.items | length %}
<div class="quick-cart-bottom">
<a href="#removeAllProductsFromCartModal" data-toggle="modal"><p>{% trans %}Remove all items{% endtrans %}</p></a>
<div>
<span>{% trans %}Total price{% endtrans %}</span>
<span>{{ order.total | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</span>
</div>
<div class="actions">
<a href="{{ path('checkout_summary', {'_locale':locale}) }}" class="btn btn-primary" type="button">{% trans %}Check out{% endtrans %}</a>
</div>
</div>
{% endif %}
{% endif %}
{% endblock quickCartContent %}
Footer
Footer layout compromises of three default blocks.
- Newsletter
- Basic store information
- Custom plugin links - lists all available plugins
footer-layout.html.twig
{% set storeInfo = alligator_store_info() %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'newsletter'} %}
<footer class="starter-footer clearfix">
<div class="wrapper">
<div class="flex-align">
<ul>
<li>{{ storeInfo.slogan }}</li>
<li><a href="tel:{{ storeInfo.storePhone }}">{{ storeInfo.storePhone }}</a></li>
<li><a href="mailto:{{ storeInfo.storeEmail }}">{{ storeInfo.storeEmail }}</a></li>
<li>{{ storeInfo.storeAddress }}</li>
</ul>
<ul>
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'footer-menu-plugin-links'} %}
</ul>
</div>
</div>
</footer>
First we have a newsletter form. Layout newsletter is set and twig template newsletter.html.twig from the Newsletter Plugin is loaded:
newsletter.html.twig
<input type="text" class="form-control" id="newsletter-email" placeholder="{% trans %}Your Email Address{% endtrans %}">
<button id="newsletter-submit" class="btn btn-subcribe" type="button">{% trans %}Submit{% endtrans %}</button>
<script src="{{ asset('Resource/Plugins/EmpireNewsletterPlugin/js/newsletter_js_compiled.js') }}"></script>
Category page
Category page is one of the most important pages of F-Webshop. It displays products from selected category, filters and attributes. Product components are displayed on this page with options that are customizable (Add to cart, Add to compare list, Add to wishlist etc.). The main twig that holds all others is category-page.html.twig from the Category Filter plugin. It's code is in the core Plugin folder, but it can be customized if needed.
product-listing-master.html.twig
This twig template contains all of the content that is necessary for category page. Product listing toolbar, product listing filters and most importantly - product components.
Function that is used:
- get_search_settings_from_cache(): gets search settings from cache
Now we will cover distinct sections of code for this twig template and provide a full code snippet at the bottom.
This section loads category filters which are set in the Dashboard (database).
<aside>
{% block sideBar %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/product-listing-filters.html.twig' %}
{% endblock %}
</aside>
Next section checks if breadcrumbs are defined or if custom breadcrumbs are defined. If not, only the page title displays. The template page-breadcrumb is in the core plugin.
{% if category is defined and category and category.breadcrumb %}
{% include '@breadcrumbTemplate/page-breadcrumb.html.twig' with {'breadcrumbs': category.breadcrumb} %}
{% elseif customBreadcrumbs is defined %}
{% include '@breadcrumbTemplate/page-breadcrumb.html.twig' with {'breadcrumbs': customBreadcrumbs} %}
{% else %}
<h1>{{ (pageTitle is defined ? pageTitle : 'Product listing')|trans }}</h1>
{% endif %}
First part of the next bit of code displays category description if it's defined. The second part sets a subcategories array if they are not defined.
{% if category is defined and category is not null %}
<p>{{ category.description|raw }}</p>
{% endif %}
{% if subcategories is not defined %}
{% set subcategories = [] %}
{% endif %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'category-page-before-toolbar'} %}
This big part of code displays the sorting options, list and grid toggles, active filters and product listing.
There are buttons for list and grid views on top, after that we load product-listing-toolbar where sorting options template is stored. Below sorting options we have all sorts of checks for attributes and brands which will be displayed if selected. Section with the id of product-grid-container holds all the products and loads product listing component in a grid view. Section with the id of product-list-container also lists product components but with a different view - now products are listed.
{% if products|length %}
<div>
<span>Filter</span>
<div>
<a href="#" id="btnGrid" title="Grid"><i class="fa fa-th-large"></i></a>
<a href="#" id="btnList" title="List"><i class="fa fa-list-ul"></i></a>
</div>
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/product-listing-toolbar.html.twig' %}
</div>
{% if filter and products | length %}
<div class="{{ isProductSearchPage is defined and isProductSearchPage ? 'filter-labels-search' }}">
{% if filter.brands|length %}
{% for brand in filter.brands %}
{% set brandId = brand['brandId'] is defined ? brand['brandId'] : brand['id'] %}
{% set brandChecked = false %}
{% if brandsArray is defined %}
{% if brandId in brandsArray %}
{% set brandChecked = true %}
{% endif %}
{% endif %}
<label for="brand-{{ brandId }}" {{ brandChecked ? 'style="display: block"' }} >
{{ brand.brandName }} <span><i class="fa fa-close"></i></span>
</label>
{% endfor %}
{% endif %}
{% if nameDependency is defined %}
{% set name = nameDependency | trim('_') %}
<label data-attribute-name="{{ name }}" style="display: block">
{{ name }}<span><i class="fa fa-close"></i></span>
</label>
{% endif %}
{% if filter['attrs'] is defined and filter['attrs']|length %}
{% set attributes = filter['attrs'] %}
{% elseif filter['attributeSets'] is defined and filter['attributeSets']|length %}
{% set attributes = [] %}
{% for attribute in filter['attributeSets'] %}
{% set attributes = attributes|merge(attribute['attributes']) %}
{% endfor %}
{% else %}
{% set attributes = [] %}
{% endif %}
{% if attributes|length %}
{% for key, attribute in attributes %}
{% set values = attribute['values'] is defined ? attribute['values'] : attribute %}
{% for attrValue in values %}
{% set attributeId = attribute['id'] is defined ? attribute['id'] : attrValue['attrId'] %}
{% set id = attrValue['id'] is defined ? attrValue['id'] : attrValue['attrValId'] %}
{% set attrValChecked = false %}
{% if attributeValuesArray is defined %}
{% for attrValArray in attributeValuesArray %}
{% if id in attrValArray %}
{% set attrValChecked = true %}
{% endif %}
{% endfor %}
{% endif %}
{% set printValue = true %}
{% if nameDependency is defined %}
{% for name, values in nameDependency if printValue %}
{% if id in values %}
{% set printValue = false %}
{% endif %}
{% endfor %}
{% endif %}
{% if printValue %}
<label for="attr-{{ id }}" {{ attrValChecked ? 'style="display: block"' }}>
{{ attrValue['value'] is defined ? attrValue['value'] : attrValue['attrValName'] }}
<span><i class="fa fa-close"></i></span>
</label>
{% endif %}
{% endfor %}
{% endfor %}
{% endif %}
</div>
{% endif %}
<div>
<img src="{{ asset(get_default_loader()) }}" alt="">
<div id="product-grid-container">
{% set class = 'category-product col-md-4 col-sm-6 col-xs-6' %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/component-product-template.html.twig' with
{'listTitle': (pageTitle is defined ? pageTitle : 'Product listing'), 'class': class}
%}
</div>
<div id="product-list-container">
</div>
<img src="{{ asset(get_default_loader()) }}" alt="">
</div>
{% else %}
<span>Filter</span>
{% if subcategories is defined %}
{% for subcat in subcategories if subcat.numberOfItems > 0 %}
<a href="{{ path('category_front', {slug:subcat.slug}) }}">
<span>
{% set imgPath = subcat.imagePath ? subcat.imagePath : alligator_store_info().baseLogoUrl %}
<img src="{{ imgPath }}" alt="{{ subcat.name|capitalize }}">
</span>
<span>{{ subcat.name|capitalize }}</span>
</a>
{% endfor %}
{% endif %}
{% if subcategories is not defined or subcategories | length < 1 %}
<h4>{% trans %}No products to show{% endtrans %}</h4>
{% endif %}
{% endif %}
This section checks the type of listing - it can be either infinite scroll or pagination.
{% if not searchSettings.infiniteScroll %}
{% if products | length >= 12 %}
<nav>
<ul>
<li>
<ul id="product-listing-page-pagination">
</ul>
</li>
</ul>
</nav>
{% endif %}
{% endif %}
The last block loads the necessary scripts and a twig template with variables that are neeeded (product-listing-variables.html.twig). Full product-listing-master.html.twig is displayed below:
product-listing-master.html.twig
{% extends currentTheme ~ '/Layout/master.html.twig' %}
{% set searchSettings = get_search_settings_form_cache() %}
{% block content %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'modals-all-include-scripts'} %}
{% block productListingSection %}
<section class="wrapper">
<div class="wrap">
<aside class="col-m-3">
{% block sideBar %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/product-listing-filters.html.twig' %}
{% endblock %}
</aside>
<div class="col-m-9">
{% if category is defined and category and category.breadcrumb %}
{% include '@breadcrumbTemplate/page-breadcrumb.html.twig' with {'breadcrumbs': category.breadcrumb} %}
{% elseif customBreadcrumbs is defined %}
{% include '@breadcrumbTemplate/page-breadcrumb.html.twig' with {'breadcrumbs': customBreadcrumbs} %}
{% else %}
<h1>{{ (pageTitle is defined ? pageTitle : 'Product listing')|trans }}</h1>
{% endif %}
{% if category is defined and category is not null %}
<p>{{ category.description|raw }}</p>
{% endif %}
{% if subcategories is not defined %}
{% set subcategories = [] %}
{% endif %}
{% if products|length %}
<div class="starter-category-toolbar">
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/product-listing-toolbar.html.twig' %}
</div>
{% if filter and products | length %}
<div class="{{ isProductSearchPage is defined and isProductSearchPage ? 'filter-labels-search' }} filter-labels">
{% if filter.brands|length %}
{% for brand in filter.brands %}
{% set brandId = brand['brandId'] is defined ? brand['brandId'] : brand['id'] %}
{% set brandChecked = false %}
{% if brandsArray is defined %}
{% if brandId in brandsArray %}
{% set brandChecked = true %}
{% endif %}
{% endif %}
<label for="brand-{{ brandId }}" {{ brandChecked ? 'style="display: block"' }} >
{{ brand.brandName }} <span><i class="fa fa-close"></i></span>
</label>
{% endfor %}
{% endif %}
{% if nameDependency is defined %}
{% set name = nameDependency | trim('_') %}
<label data-attribute-name="{{ name }}" style="display: block">
{{ name }}<span><i class="fa fa-close"></i></span>
</label>
{% endif %}
{% if filter['attrs'] is defined and filter['attrs']|length %}
{% set attributes = filter['attrs'] %}
{% elseif filter['attributeSets'] is defined and filter['attributeSets']|length %}
{% set attributes = [] %}
{% for attribute in filter['attributeSets'] %}
{% set attributes = attributes|merge(attribute['attributes']) %}
{% endfor %}
{% else %}
{% set attributes = [] %}
{% endif %}
{% if attributes|length %}
{% for key, attribute in attributes %}
{% set values = attribute['values'] is defined ? attribute['values'] : attribute %}
{% for attrValue in values %}
{% set attributeId = attribute['id'] is defined ? attribute['id'] : attrValue['attrId'] %}
{% set id = attrValue['id'] is defined ? attrValue['id'] : attrValue['attrValId'] %}
{% set attrValChecked = false %}
{% if attributeValuesArray is defined %}
{% for attrValArray in attributeValuesArray %}
{% if id in attrValArray %}
{% set attrValChecked = true %}
{% endif %}
{% endfor %}
{% endif %}
{% set printValue = true %}
{% if nameDependency is defined %}
{% for name, values in nameDependency if printValue %}
{% if id in values %}
{% set printValue = false %}
{% endif %}
{% endfor %}
{% endif %}
{% if printValue %}
<label for="attr-{{ id }}" {{ attrValChecked ? 'style="display: block"' }}>
{{ attrValue['value'] is defined ? attrValue['value'] : attrValue['attrValName'] }}
<span><i class="fa fa-close"></i></span>
</label>
{% endif %}
{% endfor %}
{% endfor %}
{% endif %}
</div>
{% endif %}
<div>
<div class="loader">
<img src="{{ asset(get_default_loader()) }}" alt="">
</div>
<div id="product-grid-container" class="wrap">
{% set class = 'category-product col-m-4 col-sm-6 col-xs-6' %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/component-product-template.html.twig' with
{'listTitle': (pageTitle is defined ? pageTitle : 'Product listing'), 'class': class} %}
</div>
<div id="product-list-container">
</div>
<div class="loader">
<img src="{{ asset(get_default_loader()) }}" alt="">
</div>
</div>
{% else %}
<span>Filter</span>
{% if subcategories is defined %}
{% for subcat in subcategories if subcat.numberOfItems > 0 %}
<a href="{{ path('category_front', {slug:subcat.slug}) }}">
<span>
{% set imgPath = subcat.imagePath ? subcat.imagePath : alligator_store_info().baseLogoUrl %}
<img src="{{ imgPath }}" alt="{{ subcat.name|capitalize }}">
</span>
<span>{{ subcat.name|capitalize }}</span>
</a>
{% endfor %}
{% endif %}
{% if subcategories is not defined or subcategories | length < 1 %}
<h4>{% trans %}No products to show{% endtrans %}</h4>
{% endif %}
{% endif %}
{% if not searchSettings.infiniteScroll %}
{% if products | length >= 12 %}
<nav>
<ul>
<li>
<ul id="product-listing-page-pagination">
</ul>
</li>
</ul>
</nav>
{% endif %}
{% endif %}
</div>
</div>
</section>
{% endblock %}
{% endblock %}
{% block scripts %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~'/Layout/Product/product-listing-variables.html.twig' %}
{% if searchSettings.infiniteScroll %}
<script>
let excludedUrlParams = ['page', 'ofs'];
</script>
{% endif %}
<script src="{{ asset('Resource/FrontendTheme/js/product_listing_scripts_compiled.js') }}"></script>
{% if searchSettings.infiniteScroll %}
<script src="{{ asset('Resource/FrontendTheme/js/product_listing_infinite_scroll_scripts_compiled.js') }}"></script>
<script>
ProductListing.prototype.initCustomPagination = function () {
paginationHandler = new InfiniteScroll(listingData, productListing, productsGridContainer, productsListContainer);
};
</script>
{% else %}
<script src="{{ asset('Resource/FrontendTheme/js/product_listing_pagination_scripts_compiled.js') }}"></script>
<script>
ProductListing.prototype.initCustomPagination = function () {
paginationHandler = new RegularPagination(listingData, productListing);
};
</script>
{% endif %}
{% block customScriptsEnd %}
{% endblock %}
{% endblock %}
product-listing-filters.html.twig
This template displays all defined filters in the sidebar. That includes price range, subcategories, brands and attributes. There are various checks and variables to comply with the user settings in the Dashboard (database).
We load three different twig tempaltes for price range, subcategories and brands. These templates are shown after the full template for product-listing-filters.html.twig
product-listing-filters.html.twig
{% set searchSettings = get_search_settings_form_cache() %}
{% set showSearchField = true %}
{% set showPriceRange = searchSettings.showPriceRange and (get_current_user_settings().showPriceIfUserNotLogged or get_logged_user()) %}
{% set filtersExist = showPriceRange or (showSearchField is defined and showSearchField) or (filter['brands'] is defined and filter['brands']|length) or (filter['attrs'] is defined and filter['attrs']|length) %}
{% if filtersExist %}
<div id="layered-filter-block">
<div>
<span>{% trans %}Filters{% endtrans %}</span>
<button id="toggle-filter-submenu"><i class="fa fa-close"></i></button>
</div>
{% if products | length and not searchSettings.ajaxFilters %}
<button id="applyFilters">{% trans %}Apply filters{% endtrans %}</button>
{% endif %}
{% if searchSettings.showSearchField %}
<div>
<div>{% trans %}Search results{% endtrans %}</div>
<ol>
<li>
<label>
<input id="custom-search-filter" placeholder="{% trans %}Search{% endtrans %}" value="{{ query is defined ? query|raw : '' }}"/>
</label>
</li>
</ol>
</div>
{% endif %}
{% if products | length %}
{% if filter['attrs'] is defined and filter['attrs']|length %}
{% set attributes = filter['attrs'] %}
{% elseif filter['attributeSets'] is defined and filter['attributeSets']|length %}
{% set attributes = [] %}
{% for attribute in filter['attributeSets'] %}
{% set attributes = attributes|merge(attribute['attributes']) %}
{% endfor %}
{% else %}
{% set attributes = [] %}
{% endif %}
<script>
var cachedLastPossibleAttrs = null;
var nameDependencyValuesByNameCached = {};
</script>
{% set searchSettings = get_search_settings_form_cache() %}
{% set subcategoriesShown = false %}
{% set priceSliderShown = false %}
{% set brandShown = false %}
{% set counter = 1 %}
{# brandPosition IS SET TO 999999999 TO ENSURE THAT BRANDS WILL BE SHOWN AT THE END #}
{% set brandPosition = category.brandPosition is defined and category.brandPosition ? category.brandPosition[0]['enabled'] : (searchSettings.brandsFirstOnSearchPage ? 1 : 999999999) %}
{% set subcategoriesPosition = category.subcategoriesPosition is defined and category.subcategoriesPosition ? category.subcategoriesPosition[0]['enabled'] : null %}
{% set priceRangePosition = category.priceRangePosition is defined and category.priceRangePosition ? category.priceRangePosition[0]['enabled'] : null %}
{% for key, attribute in attributes %}
{% set hasBaseFilterToShow = true %}
{% set incrementator = 0 %}
{% for i in 0..3 if hasBaseFilterToShow %}
{% set tmpIndex = counter + i %}
{% if subcategoriesPosition != tmpIndex and priceRangePosition != tmpIndex and brandPosition != tmpIndex %}
{% set hasBaseFilterToShow = false %}
{% endif %}
{# SHOW SUBCATEGORIES #}
{% if searchSettings.showSubcategories and not subcategoriesShown and subcategories is defined and subcategories|length and (subcategoriesPosition is null or (subcategoriesPosition == tmpIndex)) %}
{% set subcategoriesShown = true %}
{% set incrementator = incrementator + 1 %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/component-listing-filter-subcategories.html.twig' %}
{# SHOW PRICE RANGE SLIDER #}
{% elseif showPriceRange and not priceSliderShown and (priceRangePosition is null or (priceRangePosition == tmpIndex)) %}
{% set priceSliderShown = true %}
{% set incrementator = incrementator + 1 %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/component-listing-filter-price-range.html.twig' %}
{# SHOW BRANDS FILTER #}
{% elseif not brandShown and (brandPosition is null or (brandPosition == tmpIndex)) %}
{% set brandShown = true %}
{% set incrementator = incrementator + 1 %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/component-listing-filter-brands.html.twig' %}
{% endif %}
{% endfor %}
{% set counter = counter + incrementator %}
<div class="{{ filter['nameDependency'][key] is defined ? 'name-dependency-options' }}">
<div class="filter-title">{{ (attribute['name'] is defined ? attribute['name'] : key)|trans }}<i class="fa fa-chevron-down active"></i></div>
<ol>
{% if filter['nameDependency'][key] is defined %}
<script>
cachedLastPossibleAttrs = cachedLastPossibleAttrs || [];
</script>
{% for modelName, attrValues in filter['nameDependency'][key] if (nameDependency is not defined or (nameDependency is defined and nameDependency starts with modelName)) %}
{% set escapedModelName = modelName | replace({' ': '_', '&': '_'}) %}
<script>
nameDependencyValuesByNameCached['{{ escapedModelName }}'] = [];
</script>
<li>
<div data-value-key-name="{{ escapedModelName }}">
{% set attributeId = 0 %}
{% set attrValChecked = false %}
{% set openAttribute = true %}
{% if attributeValuesArray is defined %}
{% for attrValue in attrValues %}
{% set attributeId = attrValue.attrId %}
{% for attrValArray in attributeValuesArray if not attrValChecked %}
{% if attrValue.attrValId in attrValArray %}
{% set attrValChecked = true %}
{% endif %}
{% endfor %}
{% if nameDependency is defined %}
{% for name, values in nameDependency if openAttribute %}
{% if attrValue.attrValId in values %}
{% set openAttribute = false %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
{% if (nameDependency is defined and nameDependency starts with modelName) %}
{% set attrValChecked = true %}
{% endif %}
<div>
<label class="{{ (nameDependency is not defined and not attrValChecked) or (nameDependency is defined and not openAttribute) ? 'hidden' }}">
<input autocomplete="off" type="checkbox" {{ (nameDependency is defined and nameDependency starts with modelName) ? 'checked' }} class="name-dependency-value-check checkbox" data-attribute-id="{{ attributeId }}" data-attribute-name="{{ modelName }}"><span></span>
</label>
<span>{{ modelName }}</span><i class="fa fa-angle-{{ attrValChecked and openAttribute ? 'down' : 'right' }}"></i>
</div>
<script>
nameDependencyValuesByNameCached['{{ escapedModelName }}'] = [{% for attrValue in attrValues %}{{ attrValue.attrValId }}{% if not loop.last %},{% endif %}{% endfor %}];
</script>
{% if not (attrValChecked and openAttribute) %}
<script type="text/twig" id="name-dependency-values-{{ escapedModelName }}">
{% endif %}
<ul class="{{ attrValChecked and openAttribute ? 'open' }}">
{% for attrValue in attrValues %}
{% set attrValChecked = false %}
{% if attributeValuesArray is defined %}
{% for attrValArray in attributeValuesArray %}
{% if attrValue.attrValId in attrValArray %}
{% set attrValChecked = true %}
{% endif %}
{% endfor %}
{% endif %}
<li>
<label>
<input autocomplete="off" {{ attrValChecked ? 'checked' }}
type="checkbox" class="attribute-value-check checkbox"
id="attr-{{ attrValue.attrValId }}"
data-id="{{ attrValue.attrValId }}"
data-attribute-id="{{ attrValue.attrId }}"
data-attribute-name="{{ modelName }}"><span>{{ attrValue.attrValName }}</span>
</label>
</li>
{% endfor %}
</ul>
{% if not (attrValChecked and openAttribute) %}
</script>
{% endif %}
</div>
</li>
{% endfor %}
{% else %}
{% set values = attribute['values'] is defined ? attribute['values'] : attribute %}
{% for attrValue in values %}
{% set attributeId = attribute['id'] is defined ? attribute['id'] : attrValue['attrId'] %}
{% set id = attrValue['id'] is defined ? attrValue['id'] : attrValue['attrValId'] %}
{% set attrValChecked = false %}
{% if attributeValuesArray is defined %}
{% for attrValArray in attributeValuesArray %}
{% if id in attrValArray %}
{% set attrValChecked = true %}
{% endif %}
{% endfor %}
{% endif %}
<li>
<label>
<input autocomplete="off" {{ attrValChecked ? 'checked' }} type="checkbox" id="attr-{{ id }}" class="attribute-value-check" data-id="{{ id }}" data-attribute-id="{{ attributeId }}"><span>
{{ attrValue['value'] is defined ? attrValue['value'] : attrValue['attrValName'] }}</span>
</label>
</li>
{% endfor %}
{% endif %}
</ol>
</div>
{% set counter = counter + 1 %}
{% endfor %}
{% set tmpSortArray = [brandPosition, subcategoriesPosition, priceRangePosition] %}
{% for index in tmpSortArray | sort %}
{% if searchSettings.showSubcategories and not subcategoriesShown and subcategories is defined and subcategories|length and (subcategoriesPosition is null or index == subcategoriesPosition) %}
{% set subcategoriesShown = true %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/component-listing-filter-subcategories.html.twig' %}
{% endif %}
{% if showPriceRange and not priceSliderShown and (priceRangePosition is null or index == priceRangePosition) %}
{% set priceSliderShown = true %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/component-listing-filter-price-range.html.twig' %}
{% endif %}
{% if not brandShown and filter['brands'] and (brandPosition is null or index == brandPosition) %}
{% set brandShown = true %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/component-listing-filter-brands.html.twig' %}
{% endif %}
{% endfor %}
{% endif %}
{% if attributes is defined and attributes | length %}
<button class="btn btn-primary" id="resetAllFilters">{% trans %}Reset all{% endtrans %}</button>
{% endif %}
</div>
<script>
$(document).on('click', '.name-dependency-wrapper', function () {
var el = $(this).closest('.name-dependency');
if ($(this).find('.label--checkbox').hasClass('hidden')) {
if (!el.find('ul').length) {
var attrName = $(this).find('.name-dependency-value-check').data('attribute-name').replace(' ', '_').replace('&', '_');
$(el).append( $('#name-dependency-values-' + attrName)[0].innerHTML );
if (cachedLastPossibleAttrs && cachedLastPossibleAttrs.length) {
el.find('ul .attribute-value-check').each(function (i, el) {
var elId = parseInt($(el).data('id'));
cachedLastPossibleAttrs.indexOf(elId) === -1 ? $(el).closest('.item').addClass('disabled') : $(el).closest('.item').removeClass('disabled');
});
}
}
} else {
var attributeId = null;
var hasChecked = false;
el.find('ul .attribute-value-check').each(function (i, el) {
attributeId = attributeId || $(el).data('attribute-id');
if (listingData.attributeValuesArray[attributeId] !== undefined && listingData.attributeValuesArray[attributeId].length && listingData.attributeValuesArray[attributeId].indexOf(parseInt($(el).data('id'))) !== -1) {
hasChecked = true;
return false;
}
});
if (!hasChecked) {
el.find('ul').remove();
}
}
$(this).find('.label--checkbox').toggleClass('hidden');
el.find('ul').toggleClass('open');
el.find('i').toggleClass('fa-angle-down fa-angle-right ');
});
$(document).ready(function () {
var filterSidebarHeight = $("#layered-filter-block .block-content").height();
var productGridHeight = $("#product-grid-container").height();
var filterSidebar = $("#layered-filter-block");
if(filterSidebarHeight > productGridHeight) {
filterSidebar.addClass('hide-additional-filters');
filterSidebar.css('max-height', productGridHeight+'px');
}
$('#resetAllFilters').on('click', function () {
let additionalParams = {};
if (typeof pageTitle !== 'undefined') {
additionalParams['page-title'] = pageTitle;
}
listingData.attributeValuesArray = null;
listingData.brandsArray = null;
listingData.page = 1;
listingData.ofs = 12;
listingData.sortParameters = null;
listingData.nameDependency = null;
listingData.products = null;
changeUrl(null, serializeSearchObjectForUrl(listingData, typeof excludedUrlParams !== 'undefined' ? excludedUrlParams : [], false), additionalParams);
window.location.reload();
})
});
</script>
{% endif %}
The template below simply loads all subcategories of the current category.
component-listing-filter-subcategories.html.twig
<div>
<div class="filter-title">
{% trans %}Subcategories{% endtrans %}
<i class="fa fa-chevron-down active"></i>
</div>
<ul>
{% for subcat in subcategories %}
<li>
<a href="{{ permalink_path({'_locale': locale, 'entity': subcat, 'permalink': translate_category_permalink(subcat.id)}, 'category') }}">
{{ subcat.name|capitalize }} <span>{{ subcat.numberOfItems }}</span>
</a>
</li>
{% endfor %}
</ul>
</div>
The template below lists all available brands.
component-listing-filter-brands.html.twig
{% if filter['brands']|length and (category.brandPosition is not defined or (category.brandPosition[2] is defined and category.brandPosition[2]['active'])) %}
<div>
<div class="filter-title">
{% trans %}Brands{% endtrans %}
<i class="fa fa-chevron-down active"></i>
</div>
<ol>
{% for brand in filter['brands'] %}
{% set brandChecked = false %}
{% set brandId = brand['brandId'] is defined ? brand['brandId'] : brand['id'] %}
{% if brandsArray is defined %}
{% if brandId in brandsArray %}
{% set brandChecked = true %}
{% endif %}
{% endif %}
<li>
<label>
<input autocomplete="off" {{ brandChecked ? 'checked' }} type="checkbox" id="brand-{{ brandId }}" data-id="{{ brandId }}"><span>{{ ('brand.' ~ brandId ~ '.name') | trans({}, 'brand-entities') }}</span>
</label>
</li>
{% endfor %}
</ol>
</div>
{% endif %}
The template below displays price range of the product list where users can set the price range for display and filtration.
component-lsiting-filter-price-range.html.twig
<div>
<div class="filter-title">{% trans %}Price range{% endtrans %}
<i class="fa fa-chevron-down active"></i>
</div>
<i class="fa fa-times-circle" id="reset-price-range-values"></i>
<div>
<div>
<label>{% trans %}Price from{% endtrans %}:<input type="number" id="input-number-min-price"></label>
<label>{% trans %}Price to{% endtrans %}:<input type="number" id="input-number-max-price"></label>
<span id="apply-input-price-filter"><i class="fa fa-search"></i></span>
</div>
<div>
<div id="price-slider"></div>
<div id="price-span">
<span id="price-span-bottom"></span>
<span id="price-span-top"></span>
</div>
</div>
</div>
</div>
product-listing-toolbar.html.twig
This template displays the sorting options for product list. Users can choose the number of displayed product and listing order by defined conditions.
Functions that are used:
- get_default_sort_field(): gets the default sort field from the database
- alligator_eval_function(): executes code inside the string
product-listing-toolbar.html.twig
{% set defaultSort = get_default_sort_field() %}
{% set searchSettings = get_search_settings_form_cache() %}
{% if not searchSettings.infiniteScroll %}
<div class="starter-product-number">
<h6>{% trans %}Per page{% endtrans %}</h6>
<select id="filter-page-ofs-dnt">
<option value="12">12</option>
<option value="24">24</option>
<option value="36">36</option>
<option value="48">48</option>
</select>
</div>
{% endif %}
<div class="starter-product-order-params">
<h6>{% trans %}Sort by{% endtrans %}</h6>
{% set renderDefault = true %}
{% set sortParamUrl = app.request.get('sortParameters') %}
{% if sortParamUrl %}
{% set renderDefault = false %}
{% set urlSortParam = alligator_eval_function(app.request.get('sortParameters')) %}
{% set defaultSort = {field: urlSortParam[0], order: urlSortParam[1] } %}
{% endif %}
<select id="filter-page-sorting-dnt">
{% if get_current_user_settings().showPriceIfUserNotLogged or get_logged_user() %}
{% if defaultSort.field == 'currentPrice' %}
<option value="currentPrice" selected>{% trans %}Price{% endtrans %}</option>
{% set renderDefault = false %}
{% else %}
<option value="currentPrice">{% trans %}Price{% endtrans %}</option>
{% endif %}
{% endif %}
{% if defaultSort.field == 'created_at' or defaultSort.field == 'createdAt' %}
<option value="createdAt" selected>{% trans %}Newest{% endtrans %}</option>
{% set renderDefault = false %}
{% else %}
<option value="createdAt">{% trans %}Newest{% endtrans %}</option>
{% endif %}
{% if defaultSort.field == 'product_name' or defaultSort.field == 'productName' %}
<option value="productName" selected>{% trans %}Product name{% endtrans %}</option>
{% set renderDefault = false %}
{% else %}
<option value="productName">{% trans %}Product name{% endtrans %}</option>
{% endif %}
{% if defaultSort.field == 'view_count' or defaultSort.field == 'viewCount' %}
<option value="viewCount" selected>{% trans %}Most Viewed{% endtrans %}</option>
{% set renderDefault = false %}
{% else %}
<option value="viewCount">{% trans %}Most Viewed{% endtrans %}</option>
{% endif %}
{% if renderDefault %}
<option value="{{ defaultSort.field }}" selected>{{ defaultSort.title|trans }}</option>
{% endif %}
</select>
</div>
<div class="starter-product-order">
<h6>{% trans %}Order{% endtrans %}</h6>
<select id="filter-page-sorting-order-dnt">
<option value="desc" {{ defaultSort.order|lower == 'desc' ? 'selected' }}>{% trans %}Descending{% endtrans %}</option>
<option value="asc" {{ defaultSort.order|lower == 'asc' ? 'selected' }}>{% trans %}Ascending{% endtrans %}</option>
</select>
</div>
<div class="starter-product-view">
<h6>{% trans %}View{% endtrans %}</h6>
<a href="#" id="btnGrid" title="Grid"><i class="fa fa-th-large"></i></a>
<a href="#" id="btnList" title="List"><i class="fa fa-list-ul"></i></a>
</div>
<script>
$(document).ready(function() {
$('#filter-page-ofs-dnt').select2({
minimumResultsForSearch: Infinity
});
$('#filter-page-sorting-dnt').select2({
minimumResultsForSearch: Infinity
});
$('#filter-page-sorting-order-dnt').select2({
minimumResultsForSearch: Infinity
});
});
</script>
Product component
Product listing master template also loads the main product component template. That is the component-product-template.html.twig which sets all required variables and includes the main HTML template for product component and that is the component-product-show.html.twig. We also have a list view, and the template for this is component-product-list-template.html.twig which includes component-product-show-list.html.twig.
Functions that are used:
- check_role_permissions(): this function check role permissions for current user role
- erp_get_calculated_prices(): this function gets erp calculated prices from the database
This template sets variables for product compare, product wishlist, add to cart button and user roles:
component-product-template.html.twig
{% set locale = app.request.locale %}
{% set user = get_logged_user() %}
{% if classCompare is not defined %}
{% set classCompare = 'product-compare' %}
{% endif %}
{% if classWishList is not defined %}
{% set classWishList = 'product-wishlist' %}
{% endif %}
{% if classAddToCart is not defined %}
{% set classAddToCart = 'product-addtocart' %}
{% endif %}
{% if class is not defined %}
{% set class = '' %}
{% endif %}
{% if user and products|length %}
{% set erp_prices = erp_get_calculated_prices(products) %}
{% endif %}
{% if imagesArray is not defined %}
{% set imagesArray = [] %}
{% endif %}
{% if user %}
{% set userRole = user.roles[0] %}
{% set canSeePriceWithRabat = check_role_permissions('can_see_price_with_rabat', userRole) %}
{% set canSeeQuantity = check_role_permissions('can_see_quantity', userRole) %}
{% endif %}
{% for product in products %}
{% if imagesArray[loop.index0] is defined %}
{% set secondProdImage = imagesArray[loop.index0] %}
{% else %}
{% set secondProdImage = NULL %}
{% endif %}
{% if startIndex is not defined %}
{% set startIndex = loop.index0 %}
{% else %}
{% set startIndex = startIndex + 1 %}
{% endif %}
{% if not user %}
{% set productPermissions = product.permissions ? (product.permissions is iterable ? product.permissions : product.permissions|split(',')) : false %}
{% set canSeePriceWithRabat = productPermissions ? 'can_see_price_with_rabat' in productPermissions : false %}
{% set canSeeQuantity = productPermissions ? 'can_see_quantity' in productPermissions : false %}
{% endif %}
{% if product.status is not defined or product.status is defined and product.status|upper == 'AVAILABLE' %}
<div class="{{ class }} ">
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/component-product-show.html.twig' with {'startIndex' : startIndex, 'listPosition': loop.index, 'secondProdImage' : secondProdImage } %}
</div>
{% endif %}
{% endfor %}
The template below is the HTML template for product component.
Functions that are used:
- alligator_can_see_product_price(): checks the database settings for user role permissions concerning price
- get_price(): gets the price from the database
- get_action_price(): gets the action price from the database
- permalink_path(): gets the permalink path from the database
- translate_product_permalink(): translates product permalink according to the current language locale
- calculate_price_percentage_discount(): calculates the defined percentage discount on the current price
- get_pav_image(): gets the product attribute value image from the database
- translate_product_description(): gets product description according to the current language
- alligator_check_product_add_to_cart_permission(): checks the defined user role permission for add to cart button
component-product-show.html.twig
{% set isXMLRequest = app.request.headers.get('X-Requested-With') == 'XMLHttpRequest' %}
{% if listTitle is not defined %}
{% set listTitle = 'Product Listing' %}
{% endif %}
{% if listPosition is not defined%}
{% set listPosition = 1 %}
{% endif %}
{% set user = get_logged_user() %}
{% set canSeePrice = alligator_can_see_product_price(product.permissions) %}
{% if canSeePrice %}
{% set finalPrice = get_price(product) %}
{% set actionPrice = get_action_price(product) %}
{% endif %}
{% set productLink = permalink_path({'_locale': locale, 'entity': product, 'permalink': translate_product_permalink(product.id)}) %}
{% set redirectPavId = null %}
{% if product.pavs is defined and product.pavs|length %}
{% set tmpPavs = product.pavs|split(',') %}
{% set tmpId = tmpPavs[0]|split(':') %}
{% set redirectPavId = tmpId[0] %}
{% set productLink = productLink ~ '?product_variant='~redirectPavId %}
{% endif %}
{% set numberInStock = (product.simpleProduct is defined ? product.simpleProduct.numberInStock : product.numberInStock ) %}
{% set labelSum = product.onAction and product.isNew %}
<div class="product-component">
<div class="{{ actionPrice is defined and actionPrice > 0 ? 'action-new' : '' }} {{ product.isNew ? 'new-product' : '' }}">
{% if actionPrice is defined and actionPrice > 0 %}
{% trans %}Action{% endtrans %} {{ calculate_price_percentage_discount(finalPrice, actionPrice) }}%
{% endif %}
</div>
<div class="{{ product.isNew ? 'new-product' : '' }}">
{% if product.isNew %}
{% trans %}New{% endtrans %}
{% endif %}
</div>
{% set images_base_url = false %}
{% if product.attr_values is defined %}
{% set images_base_url = get_pav_image(product.attr_values|split(','), product.attr_values_pavs|split(','), product.pavs|split(',')) %}
{% endif %}
{% set base_pav_url = false %}
{% for key, value in images_base_url %}
{% set base_pav_url = value['image'] %}
{% endfor %}
<a href="{{ productLink }}" >
{% if base_pav_url %}
<img id="product_main_image_{{ product.id }}" src="{{ base_pav_url }}" alt="{{ product.productName }}" class="img-responsive">
{% else %}
{% if product.baseImageUrl %}
<img id="product_main_image_{{ product.id }}" src=" {{ getDefaultExtension(product.baseImageUrl) }}" alt="{{ product.productName }}" class="img-responsive">
{% else %}
<img id="product_main_image_{{ product.id }}" src="{{ asset('Resource/FrontendTheme/images/default_product.png') }}" alt="{{ product.productName }}" class="img-responsive">
{% endif %}
{% endif %}
{% if secondProdImage is defined %}
{{ secondProdImage|raw }}
{% endif %}
</a>
<span>
{% for key, value in images_base_url if images_base_url|length > 1 %}
<img class="img-responsive pav_change_image_{{ product.id }}" data-attr_val_id="{{ value['attr_val'] }}" data-pav_val_id="{{ value['attr_val'] }}" src="{{ getDefaultExtension(value['image']) }}" alt="{{ product.productName }}" >
{% endfor %}
</span>
<div>
<h5><a href="{{ productLink }}">{{ product.sku }}</a></h5>
<h3><a href="{{ productLink }}">{{ product.productName }}</a></h3>
</div>
<div>{{ translate_product_short_description(product.id) }}</div>
<div class="price">
{% if alligator_can_see_product_price(product.permissions) %}
{% if finalPrice != NULL %}
{% if actionPrice %}
{% if not isXMLRequest %}
<span class="product-price-container product_{{ product.id }}" data-product-id="{{ product.id }}"></span>
{% else %}
<span>{{ actionPrice | alligator_json_convert() | alligator_money_format }}</span>
{% endif %}
{% if not isXMLRequest %}
<span class="product-price-container product_{{ product.id }}" data-product-id="{{ product.id }}"></span>
{% else %}
<span>{{ finalPrice | alligator_json_convert() | alligator_money_format }}</span>
{% endif %}
{% else %}
{% if not isXMLRequest %}
<span class="product-price-container product_{{ product.id }}" data-product-id="{{ product.id }}"></span>
{% else %}
<span>{{ finalPrice | alligator_json_convert() | alligator_money_format }}</span>
{% endif %}
<div class="line-through">
</div>
{% endif %}
{% endif %}
{% endif %}
</div>
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-compare-retweet-icon', 'productId': product.id, 'index': startIndex, 'promo' : 0} %}
<a class="{{ classWishList }}"><span class="flaticon-heart"></span></a>
<a class="{{ classAddToCart }} add-to-cart-{{ product.id }}" href="#">{% trans %}Add To Cart{% endtrans %}</a>
</div>
<script>
$(document).ready(function(){
$('.pav_change_image_{{ product.id }}').click(function(){
var imageSrc= $(this).attr("src");
$('#product_main_image_{{ product.id }}').attr('src',imageSrc);
});
});
var productObj = {
id: {{ product.id }},
slug: "{{ product.slug }}",
product_name: "{{ product.productName }}",
{% if canSeePrice %}
current_price : {{ finalPrice }},
{% endif %}
simple_product: {
{% if numberInStock != NULL %}
number_in_stock: {{ numberInStock }}
{% endif %}
},
quantity_step: {{ product.quantityStep ? product.quantityStep : 1 }},
preventDirectAddToCart: {{ product.preventDirectAddToCart is defined and product.preventDirectAddToCart ? 1 : 0 }}
};
var classWishlist = '{{ classWishList }}';
var classAddToCart = '{{ classAddToCart }}';
var index = {{ startIndex }};
var customBtn = new AddToCartButton( $('.product-component .'+classAddToCart)[index], productObj);
{{ product.permissions is defined and alligator_check_product_add_to_cart_permission(product.permissions) == false ? 'customBtn.setNotAllowed();' }}
allAddToCartButtons.push({id: productObj.id, btn: customBtn});
// var wishBtn = new WishlistButton($('.product-component .'+classWishlist)[index], productObj.id);
// remove this later
{% if product.preventDirectAddToCart is not defined %}
console.log('Product object does not have all fields needed. Check preventDirectAddToCart field', productObj);
{% endif %}
</script>
We also separately load Add to compare button which is placed in the product-compare-retweet-icon layout. The template product-compare-retweet-icon.html.twig is called in this layout.
product-compare-retweet-icon.html.twig
{% block compareRetweetIcon %}
<a href="" class="entry {{ classCompare }}">
<span class="flaticon-reload"></span>
</a>
<script>
var classCompare = '{{ classCompare }}';
var productObjCompare = {
id: {{ productId }}
};
var compareBtn = new CompareButton($('.'+classCompare)[{{ index }}], productObjCompare);
</script>
{% endblock %}
product-listing-pagination.html.twig
{% if listView %}
{% include currentTheme ~ '/Layout/Product/component-product-list-template.html.twig' %}
{% else %}
{% set class = 'category-product col-m-4 col-sm-6 col-xs-6' %}
{% include app.request.session.get('_theme') ~'/Layout/Product/component-product-template.html.twig' with {'listTitle': listingTitle is defined ? listingTitle : 'Product listing'} %}
{% endif %}
component-product-list-template.html.twig
{% set locale = app.request.locale %}
{% set user = get_logged_user() %}
{% if classCompare is not defined %}
{% set classCompare = 'product-compare-list' %}
{% endif %}
{% if classWishList is not defined %}
{% set classWishList = 'product-wishlist-list' %}
{% endif %}
{% if classAddToCart is not defined %}
{% set classAddToCart = 'product-addtocart-list' %}
{% endif %}
{% if user and products|length %}
{% set erp_prices = erp_get_calculated_prices(products) %}
{% endif %}
<ol>
{% for product in products %}
{% if startIndex is not defined %}
{% set startIndex = loop.index0 %}
{% else %}
{% set startIndex = startIndex + 1 %}
{% endif %}
{% if product.status is not defined or product.status is defined and product.status|upper == 'AVAILABLE' %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/Product/component-product-show-list.html.twig' with {'startIndex' : startIndex, 'listPosition': loop.index} %}
{% endif %}
{% endfor %}
</ol>
component-product-show-list.html.twig
{% if listTitle is not defined%}
{% set listTitle = 'Product Listing' %}
{% endif %}
{% if listPosition is not defined%}
{% set listPosition = 1 %}
{% endif %}
<li>
<div class="component-product-list">
{% if product.onAction is defined %}
{% if product.onAction %}
<span>{% trans %}Action{% endtrans %}</span>
{% endif %}
{% endif %}
{% if product.isNew is defined %}
{% if product.isNew %}
<span>{% trans %}New{% endtrans %}</span>
{% endif %}
{% endif %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'gift-products-template'} %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-image-additionals'} %}
<a href="{{path('product_front_slug',{'_locale': locale, 'slug': product.slug})}}">
{% if product.baseImageUrl %}
<img src=" {{ product.baseImageUrl }}" alt="{{ product.productName }}" class="img-responsive">
{% else %}
<img src="{{ asset('Resource/FrontendTheme/images/default_product.png') }}" alt="{{ product.productName }}" class="img-responsive">
{% endif %}
</a>
<div>
<a href="{{path('product_front_slug',{'_locale': locale, 'slug': product.slug})}}">{{ ('product.' ~ product.id ~ '.name') | trans({}, 'product-entities') }}</a>
<p>{{ product.sku }}</p>
<div>
{% if alligator_can_see_product_price(product.permissions) %}
{% if product.priceFrom is defined and product.priceFrom > 0 and product.priceTo is defined and product.priceTo > 0 %}
<span>{{ product.priceFrom | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }} - {{ product.priceTo | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }} </span>
{% else %}
{% set finalPrice = get_price(product) %}
{% if finalPrice != NULL %}
{% if product.onAction is defined and product.onAction and product.priceOnAction is defined %}
<span>{{ product.priceOnAction | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</span>
{% endif %}
<span {{ product.onAction is defined and product.onAction ? 'style="text-decoration: line-through; font-size: 11px;"' }}>{{ finalPrice | alligator_json_convert() | alligator_money_format }}</span>
{% endif %}
{% endif %}
{% endif %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-info-additionals'} %}
</div>
<div class="rateit" data-rateit-value="{{ product.reviewRating }}" data-rateit-ispreset="true" data-rateit-resetable="false" data-rateit-readonly="true"></div>
<div>{{ product.description }}</div>
<div class="product-item-actions">
<button type="button" class="btn-cart {{ classAddToCart }}">{% trans %}Add to Cart{% endtrans %}</button>
<a class="btn-wishlist {{ classWishList }}" href="">{% trans %}wishlist{% endtrans %}</a>
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-compare-retweet-icon', 'productId': product.id, 'index': startIndex, 'promo' : 0} %}
</div>
</div>
</div>
</li>
<script>
var productObj = {
id: {{ product.id }},
slug: "{{ product.slug }}",
product_name: "{{ product.productName }}",
{% if alligator_can_see_product_price(product.permissions) %}
current_price : {{ get_price(product) }},
{% endif %}
simple_product: {
{% if product.numberInStock != NULL %}
number_in_stock: {{ product.numberInStock }}
{% endif %}
},
quantity_step: {{ product.quantityStep ? product.quantityStep : 1 }}
};
productObj.list = "{{ listTitle }}";
var classWishlist = '{{ classWishList }}';
var classAddToCart = '{{ classAddToCart }}';
var index = {{ startIndex }};
var btn = new AddToCartButton( $('.product-item-actions .'+classAddToCart)[index], productObj);
{{ product.permissions is defined and alligator_check_product_add_to_cart_permission(product.permissions) == false ? 'btn.setNotAllowed();' }}
var wishBtn = new WishlistButton($('.product-item-actions .'+classWishlist)[index], productObj.id);
</script>
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'component-product-show-scripts', 'productId': product.id, 'position': listPosition} %}
Product page
This is the page where all of the product information is displayed. This includes product images, code, name, category, price, action buttons, description and any other relevant information. The main template is product-page.html.twig.
These functions are used:
- translate_product_seo_title(): gets the translation for product SEO title from the database according to the current language locale
- translate_product_seo_keywords(): gets the translation for product SEO keywords from the database according to the current language locale
- translate_product_seo_description(): gets the translation for product SEO description from the database according to the current language locale
In the next blocks, a few distinct code snippets will be covered. First comes the headBlock which sets layout for Google rich snippet and Facebook Pixel scripts.
{% block headBlock %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-page-head-scripts'} %}
{% endblock %}
After the breadcrumbs, we are loading two twig templates. The first one is product-content.html.twig which contains main product information. The other one is product-full-width-layout.html.twig. These two templates are covered later.
<div class="wrap">
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Product/product-content.html.twig' with { 'product':product } %}
</div>
<div class="wrap">
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Product/product-full-width-layout.html.twig' %}
</div>
The scripts are loaded in the end.
product-page.html.twig
{% extends constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/master.html.twig' %}
{% set productId = product.id %}
{% block seo %}
<!-- meta description -->
{% set seoTitle = translate_product_seo_title(productId) %}
<title>{{ seoTitle == 'product.' ~ productId ~ '.seo-title' ? translate_product(productId) : seoTitle }}</title>
<meta name="keywords" content="{{ translate_product_seo_keywords(productId) }}">
<meta name="description" content="{{ translate_product_seo_description(productId) }}">
{% endblock %}
{% block socialCustom %}
<!-- social meta data -->
<meta property="og:title" content="{{ translate_product_seo_title(productId) }}"/>
<meta property="og:type" content="article" />
{% if product.baseImageUrl %}
<meta property="og:image" content="{{ app.request.schemeAndHttpHost ~ product.baseImageUrl }}"/>
{% endif %}
<meta property="og:description" content="{{ translate_product_seo_description(productId) }}"/>
{% endblock socialCustom %}
{% set userSettings = get_current_user_settings() %}
{% block headBlock %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-page-head-scripts'} %}
{% endblock %}
{% block content %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'modals-all-include-scripts'} %}
<main class="wrapper">
<ol>
{% set category = product.categories | length ? product.categories[0] : null %}
{% set additionalBreadcrumb = [{'route': 'product_front_slug', 'routeParam': product.slug, 'routeTitle': ('product.' ~ product.id ~ '.name') | trans({}, 'product-entities')}] %}
{% if category is defined and category and category.breadcrumb %}
{% include '@breadcrumbTemplate/page-breadcrumb.html.twig' with {'breadcrumbs': category.breadcrumb, 'additional_breadcrumbs': additionalBreadcrumb } %}
{% endif %}
</ol>
<div class="wrap">
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Product/product-content.html.twig' with { 'product':product } %}
</div>
</main>
<script>
var productId = {{ product.id }};
var productSlug = "{{ product.slug }}";
var showPriceToUser = false;
{% if get_current_user_settings().showPriceIfUserNotLogged or get_logged_user() %}
showPriceToUser = true;
{% endif %}
</script>
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Product/product-scripts.html.twig' with {'product': product } %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Product/product-plugin-scripts-layout.html.twig' with {'product': product } %}
{% endblock %}
{% block scripts %}
<script src="{{ asset('Resource/FrontendTheme/js/elevate_zoom_compiled.js') }}"></script>
{% endblock %}
Twig template product-content.html.twig is the main child template of product page template. It contains product slider and all relevant product information along with action buttons.
product-content.html.twig
<div class="col-m-9">
<div class="wrap">
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-slider'} %}
<div class="col-m-6">
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-information'} %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'shopping-cart-wishlist'} %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/ShareButtons/share-buttons.html.twig' with {'twitterTitle': product.productName } %}
</div>
</div>
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product360-gallery'} %}
</div>
<div class="col-m-3">
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-page-right-alignment'} %}
</div>
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Product/product-page-display-bundle-product.html.twig' %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-description'} %}
The first include of this template is product slider from Slider Plugin which contains an HTML template for synced Owl carousel image slider. Template checks the number of available images on the products, and if it's possible it creates a synced slider with one image in focus and a track of thumbnails for control.
product-slider.html.twig
{% block productSlider %}
<div class="col-m-6">
<div id="sync1" class="owl-carousel">
{% for image in product.images %}
<div>
<img src="{{ image.webPath }}" alt="{{ product.productName|capitalize }}" class="img-responsive" />
</div>
{% endfor %}
</div>
{% if product.images | length>1 %}
<div id="sync2" class="owl-carousel">
{% for image in product.images %}
<img src="{{ image.webPath }}" alt="tech 2" class="img-responsive"/>
{% endfor %}
</div>
{% endif %}
</div>
{% endblock %}
The next twig that is included via layout is product-information.html.twig from the Product Information plugin. Here we perform checks for user role permissions and display product name, quantity and price. After that, we include the layouts product-page-additional-info and product-info-availability.
product-information.html.twig
{% block productInformationContent %}
<h2>{{ ('product.' ~ product.id ~ '.name') | trans({}, 'product-entities') | capitalize }}</h2>
<div>
{% set user = get_logged_user() %}
{% if user %}
{% set userRole = user.roles[0] %}
{% set canSeeQuantity = check_role_permissions('can_see_quantity', userRole) %}
{% else %}
{% set productPermissions = product.permissions %}
{% set canSeeQuantity = 'can_see_quantity' in productPermissions %}
{% endif %}
{% if alligator_can_see_product_price(product.permissions) %}
{% set finalPrice = get_price(product) %}
<div>
{% if not product.onAction %}
<p {{ product.onAction ? 'style="text-decoration: line-through; font-size:14px;"' }}>
{{ finalPrice | alligator_json_convert() | alligator_money_format }}
</p>
{% else %}
<span style="text-decoration: line-through; font-size:14px;">
{{ finalPrice | alligator_json_convert() | alligator_money_format }}
</span>
<p>{{ product.priceOnAction | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</p>
<div>{% trans %}On action{% endtrans %}</div>
{% endif %}
</div>
{% endif %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-info-availability'} %}
</div>
<p>{{ product.description }}</p>
{% endblock %}
product-info-availability.html.twig is used for a more detailed view of quantity.
Function that are used:
- can_see_quantity_light: this function checks user role permissions for viewing minimum and maximum quantity
- can_see_quantity: this function checks user role permissions for viewing quantity
product-info-avilability.html.twig
{% set informationProduct = product is defined ? product : productAttrVal.product %}
{% set informationQuantity = product is defined ? product.simpleProduct.numberInStock : 0 %}
{% set informationMinQuantity = product is defined ? product.simpleProduct.minQuantity : 1 %}
{% if productAttrVal is defined %}
{% set informationQuantity = productAttrVal.quantity %}
{% set informationMinQuantity = productAttrVal.minQuantity %}
{% elseif product is defined %}
{% if product.productAttributesValues | length %}
{% for prodAttrVal in product.productAttributesValues if prodAttrVal.default %}
{% set informationQuantity = prodAttrVal.quantity %}
{% set informationMinQuantity = prodAttrVal.minQuantity %}
{% endfor %}
{% endif %}
{% endif %}
<div>
{% if can_see_quantity_light(informationProduct.permissions) %}
{% set simpleProduct = informationProduct.simpleProduct %}
{% set flagStatus = 'red' %}
{% set infoText = 'not in stock' %}
{% if informationQuantity > simpleProduct.maxQuantityLightValue %}
{% set flagStatus = 'green' %}
{% set infoText = 'in stock' %}
{% elseif informationQuantity > simpleProduct.minQuantityLightValue %}
{% set flagStatus = 'yellow' %}
{% set infoText = 'low stock' %}
{% endif %}
<span><i class="fa fa-flag {{ flagStatus }}"></i></span>
<span>{{ infoText|trans }}</span>
{% else %}
{% if can_see_quantity(informationProduct.permissions) %}
{% if informationQuantity >= informationMinQuantity %}
<i class="fa fa-check-square-o" aria-hidden="true"></i> {% trans %}in stock{% endtrans %}
{% else %}
<i class="fa fa-square-o" aria-hidden="true"></i> {% trans %}not in stock{% endtrans %}
{% endif %}
{% endif %}
{% endif %}
</div>
The shopping-cart-wishlist.html.twig is the main template for action buttons. First we have Add to cart button, Wishlist button and Product question button. After that, we set a layout for additional-product-actions which includes Tell a friend action button which sends information on the product to the desired email. And at last, we have a layout addToCompare which includes Add to compare button.
shopping-cart-wishlist.html.twig
{% block shoppingWishlist %}
<div class="actions">
{% block shoppingCartMainIcons %}
<a class="cart-btn btn btn-primary" href="#" id="shopping-cart-btn"><i class="fa fa-shopping-cart"></i></a>
<button type="button" class="btn-wishlist btn btn-primary" id="heart-icon"><i class="fa fa-heart"></i></button>
<a class="btn btn-primary" href="{{ path('user_add_ticket', {product: product.id}) }}" id="ticket-icon"><i class="fa fa-pencil-square-o"></i></a>
{% endblock shoppingCartMainIcons %}
{% block shoppingCartAdditionalIcons %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'additional-product-actions'} %}
{% endblock shoppingCartAdditionalIcons %}
{% block shoppingCartAddToComape %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'addToCompare'} %}
{% endblock %}
</div>
{% endblock %}
email-friend-modal.html.twig
{% block emailFriendModalContent %}
{% block emilFriendButton %}
<button type="button" data-toggle="modal" data-target="#emailFriendModal" title="{% trans %}Tell a friend{% endtrans %}">
<i class="fa fa-envelope-o"></i>
</button>
{% endblock emilFriendButton %}
{% block emilFriendModal %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireEmailFriendPlugin/views/email-friend-only-modal.html.twig' %}
{% endblock emilFriendModal %}
{% endblock %}
email-friend-only-modal.html.twig
{% block emailFriendModal %}
<div class="modal fade" id="emailFriendModal" tabindex="-1" role="dialog" aria-labelledby="emailFriendModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="emailFriendModalLabel">{% trans %}Tell a friend{% endtrans %} <span class="close" data-dismiss="modal" aria-label="{% trans %}Close{% endtrans %}">
<i class="fa fa-close"></i>
</span>
</h4>
</div>
<div class="modal-body">
<form id="emailFriendForm">
<div class="form-group">
<label for="emailFriendYourMail">{% trans %}E-Mail{% endtrans %}</label>
<input type="email" {% if get_logged_user() %}disabled{% endif %} class="form-control" id="emailFriendYourMail" value="{{ get_logged_user() ? get_logged_user().email}}">
</div>
<div class="form-group">
<label for="emailFriendToMail">{% trans %}Friends Email{% endtrans %}</label>
<input type="text" class="form-control" id="emailFriendToMail" >
</div>
<div class="form-group">
<label for="emailFriendSubject">{% trans %}Subject{% endtrans %}</label>
<input type="text" class="form-control" id="emailFriendSubject" >
</div>
<div class="form-group">
<label for="emailFriendContent">{% trans %}Content{% endtrans %}</label>
<textarea class="form-control" id="emailFriendContent" rows="3"></textarea>
</div>
</form>
<div class="modal-footer">
{% if product is defined %}
<button type="button" class="send-mail-btn" data-product-id="{{ product.id}}">{% trans %}Send{% endtrans %}</button>
{% else %}
<button type="button" class="send-mail-btn" data-product-id="-1">{% trans %}Send{% endtrans %}</button>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
add-to-compare.html.twig
{% block addToCompareContent %}
<a href="#" id="add-to-compare" class="btn-compare btn btn-primary" title="{% trans %}Compare{% endtrans %}">
<i class="fa fa-refresh"></i>
</a>
{% endblock %}
In product-content twig template we also include share-buttons template which displays share buttons for popular social networks.
Functions that are used:
- getSchemeAndHttpHost(): gets the scheme and HTTP host
- get_social_networks(): gets the social networks from the database
share-buttons.html.twig
<div>
{% if pageUrl is not defined %}
{% if app.request.attributes.get('permalink') is defined %}
{% set pageUrl = app.request.getSchemeAndHttpHost() ~ app.request.attributes.get('permalink') %}
{% else %}
{% set pageUrl = url(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')) %}
{% endif %}
{% endif %}
{% set sn = get_social_networks() %}
{% if sn.snFacebook %}
<a href="#" class="entry entry-social-share-button-facebook btn btn-info" target="_blank">
<i class="fa fa-facebook"></i>
</a>
{% endif %}
{% if sn.snTwitter %}
<a href="#" class="entry entry-social-share-button-twitter btn btn-info" target="_blank">
<i class="fa fa-twitter"></i>
</a>
{% endif %}
{% if sn.snGplus %}
<a href="#" class="entry entry-social-share-button-google btn btn-info" target="_blank">
<i class="fa fa-google-plus"></i>
</a>
{% endif %}
</div>
<script>
$('.entry-social-share-button-facebook').on('click', function (e) {
e.preventDefault();
var url = 'http://www.facebook.com/sharer.php?s=100&p[title]={{ 'title' }}&p[summary]={{ 'descr' }}&p[url]={{ pageUrl }}&p[images][0]={{ 'image' }}';
openShareModal(url, 'Facebook Share', 520, 450);
});
$('.entry-social-share-button-twitter').on('click', function (e) {
e.preventDefault();
var title = '{% if twitterTitle is defined %}{{ twitterTitle }}{% else %}Twitter Share{% endif %}';
var url = 'https://twitter.com/intent/tweet?url={{ pageUrl }}&text=' + title;
openShareModal(url, title, 520, 450);
});
$('.entry-social-share-button-google').on('click', function (e) {
e.preventDefault();
var url = 'https://plus.google.com/share?url={{ pageUrl }}';
openShareModal(url, 'Google+ Share', 520, 450);
});
function openShareModal(url, title, winWidth, winHeight){
var winTop = (screen.height / 2) - (winHeight / 2);
var winLeft = (screen.width / 2) - (winWidth / 2);
window.open(url, title, 'top=' + winTop + ',left=' + winLeft + ',toolbar=0,status=0,width=' + winWidth + ',height=' + winHeight);
}
</script>
Next template that we are including is Product 360 which enables viewing product in 360 degrees mode if available.
Function that is used:
- alligator_product360(): gets the product with 360 functionality from the database
gallery-360.html.twig
{% set product360 = alligator_product360(product.id) %}
{% block galleryContent %}
{% if product360|length > 1 %}
<div class="modal fade" id="360modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div id="imageHolder" style="overflow: hidden; background: #fff;text-align: center; width: 100%">
<img src="{{ product360[0]['imageWebPath'] }}" class="active" alt="" height="100%" draggable="false" style="position: relative; left: 0; top: 0;">
{% for image in product360 %}
{% if loop.index > 1 %}
<img src="{{ image['imageWebPath'] }}" class="" alt="" height="100%" draggable="false" style="position: relative; left: 0; top: 0; display: none">
{% else %}
{% endif %}
{% endfor %}
<a data-dismiss="modal"><i class="fa fa-times-circle"></i></a>
</div>
<div>
{% if product360[0]['slider']%}
<input type="range" id="rotateSlider" min="0" max="36">
{% endif %}
{% if product360[0]['buttons'] %}
<div>
<button id="rot-left"><i class="fa fa-chevron-left"></i></button>
<button id="rot-play-pause"><i class="fa fa-{{ product360[0]['autoPlay']|json_encode ? 'pause' : 'play' }}"></i></button>
<button id="rot-right"><i class="fa fa-chevron-right"></i></button>
</div>
{% endif %}
<div>
<span>{% trans %}Drag to rotate{% endtrans %}</span>
<img src="{{ asset('Resource/FrontendTheme/images/icon/mouse-icon.png') }}" alt="">
</div>
</div>
{% block galleryScript %}
<script>
// init images array
var imagesArray = [];
{% for item in product360 %}
imagesArray.push({
name: "{{ item['imageWebPath'] }}"
});
{% endfor %}
var autoplayBool = {{ product360[0]['autoPlay']|json_encode }};
var autoplaySpeed = {{ product360[0]['autoPlaySpeed']|json_encode }};
var buttons = {{ product360[0]['buttons']|json_encode }};
var slider = {{ product360[0]['slider']|json_encode }};
</script>
{% endblock galleryScript %}
</div>
</div>
</div>
{% endif %}
{% endblock %}
Product relationship plugin is loaded in the product-page-right-alignment layout. It displays connected products on the right side by default.
Function that is used:
- get_relationship_names_for_product(): gets the product relationship names from the database
product-page-right-alignment-content.html.twig
{% set relationships = get_relationship_names_for_product(product.id) %}
{% if relationships %}
<div id="product-relationship-holder">
{% if relationships|length %}
{% for rel in relationships %}
{% if rel.position == 'right_alignment' %}
{% if rel.type == 'collapsible' %}
{% include '@productRelationshipPluginViews/layouts/components/product-right-alignment-collapsable.html.twig' %}
{% else %}
{% include '@productRelationshipPluginViews/layouts/components/product-right-alignment-owl.html.twig' %}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
</div>
<script>
var relationshipNames = {{ relationships|json_encode|raw }};
</script>
{% else %}
<script>
var relationshipNames = [];
</script>
{% endif %}
Function that is used:
- get_quantity_discounts(): gets the quantity discount from the database
product-page-right-alignment-template.html.twig
<div class="item">
{% for prod in products %}
<div id="product-sidebar-item-{{ prod.id }}">
<div>
{% set productDiscounts = get_quantity_discounts(prod.id) %}
{% if productDiscounts.percent is defined and productDiscounts.percent %}
<a href="#">
{% for qty, val in productDiscounts.percent %}
<span>{{ qty }} {% trans %}pcs.{% endtrans %} - {{ val }}%</span>
{% endfor %}
</a>
{% endif %}
<a href="{{ path('product_front_slug', {'_locale': app.request.locale, 'slug':prod.slug}) }}">
{% if prod.baseImageUrl %}
<img src=" {{ getDefaultExtension(prod.baseImageUrl) }}" alt="{{ prod.productName }}">
{% else %}
<img src="{{ asset('Resource/FrontendTheme/images/default_product.png') }}" alt="{{ prod.productName }}">
{% endif %}
</a>
<div>
<a title="{{ ('product.' ~ prod.id ~ '.name') | trans({}, 'product-entities') }}" href="{{ path('product_front_slug', {'_locale': app.request.locale, 'slug':prod.slug}) }}">{{ ('product.' ~ prod.id ~ '.name') | trans({}, 'product-entities') }} </a>
<div>{{ prod.sku }}</div>
{% if alligator_can_see_product_price(prod.permissions) %}
{% if prod.priceFrom is defined and prod.priceFrom > 0 and prod.priceTo is defined and prod.priceTo > 0 %}
<span>{{ prod.priceFrom | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}
- {{ prod.priceTo | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }} </span>
{% else %}
<span>{{ get_price(prod) | alligator_json_convert() | alligator_money_format }}</span>
{% endif %}
{% endif %}
</div>
</div>
<div>
<button type="button" class="btn-cart">{% trans %}Add to Cart{% endtrans %}</button>
<a class="btn-wishlist" href=""><i class="fa fa-heart"></i></a>
{% if can_see_quantity_light(prod.permissions) %}
{% if prod.maxQuantityLightValue is defined and prod.minQuantityLightValue is defined %}
{% set flagStatus = 'red' %}
{% if prod.numberInStock > prod.maxQuantityLightValue %}
{% set flagStatus = 'green' %}
{% elseif prod.numberInStock > prod.minQuantityLightValue %}
{% set flagStatus = 'yellow' %}
{% endif %}
<a><i class="fa fa-flag {{ flagStatus }}"></i></a>
{% endif %}
{% endif %}
</div>
</div>
<script>
$(document).ready(function() {
var productObj = {
id: {{ prod.id }},
slug: "{{ prod.slug }}",
product_name: "{{ prod.productName }}",
{% if alligator_can_see_product_price(prod.permissions) %}
current_price : {{ get_price(prod) }},
{% endif %}
simple_product: {
{% if prod.numberInStock != NULL %}
number_in_stock: {{ prod.numberInStock }}
{% endif %}
},
quantity_step: {{ prod.quantityStep ? prod.quantityStep : 1 }},
preventDirectAddToCart: {{ prod.preventDirectAddToCart is defined and prod.preventDirectAddToCart ? 1 : 0 }}
};
productObj.list = "Sidebar Products";
var btn = new AddToCartButton($('#product-sidebar-item-{{ prod.id }} .btn-cart')[0], productObj);
{{ product.permissions is defined and alligator_check_product_add_to_cart_permission(product.permissions) == false ? 'btn.setNotAllowed();' }}
allAddToCartButtons.push({id: productObj.id, btn: btn});
var wishBtn = new WishlistButton($('#product-sidebar-item-{{ prod.id }} .btn-wishlist')[0], productObj.id);
});
</script>
{% endfor %}
</div>
product-right-alignment-owl.html.twig
<div id="relationship-{{ rel.id }}" class="{{ rel.position }}">
<a href="{{ path('product_relationship_search', {'_locale': app.request.locale, 'relationshipId': rel.id, 'productId': product.id, 'page-title': (rel.name | trans) ~ ' - ' ~ product.productName }) }}">
{{ rel.name | trans }}
</a>
<div>
<div id="cr-items-loader-{{ rel.id }}">
<img src="{{ asset(get_default_loader()) }}" alt="">
</div>
<ol class="owl-carousel" id="relationship-products-carousel-{{ rel.id }}" data-relationship="{{ rel.id }}" data-nav="true" data-dots="false" data-margin="10" data-items="1" hidden>
</ol>
<div id="cr-no-products-{{ rel.id }}" hidden>
<p>{% trans %}No related products{% endtrans %}</p>
</div>
</div>
</div>
After that we are including the bundle product functionality. It displays group products.
product-page-display-bundle-product.html.twig
{% if product.groupProduct %}
<div class="table col-m-12">
{% if product.groupProduct.groupedSimpleProducts | length >0 %}
<h1>{% trans %}Bundle product{% endtrans %}</h1>
<div id="product-page-bundle-products-holder">
<div class="loader">
<img src="{{ asset(get_default_loader()) }}" alt="">
</div>
</div>
{% endif %}
</div>
{% endif %}
Next two twig templates are related to the product attributes. Basically, they are loaded if product has attributes that changes its images or price.
product-slider-complete.html.twig
<div class="owl-carousel thumbnails_carousel" id="thumbnails" data-nav="true" data-dots="false" data-margin="15" data-responsive='{"0":{"items":3},"480":{"items":4},"600":{"items":5},"768":{"items":3}}'>
{% for image in images %}
<a href="#" data-image="{{ image.webPath }}" data-zoom-image="{{ image.webPath }}">
<img src="{{ image.webPath }}" data-large-image="{{ image.webPath }}" alt="{{ image.webPath }}">
</a>
{% endfor %}
</div>
product-info-price-template.html.twig
{% set user = get_logged_user() %}
{% if user %}
{% set userRole = user.roles[0] %}
{% set canSeeQuantity = check_role_permissions('can_see_quantity', userRole) %}
{% else %}
{% set productPermissions = productAttrVal.product.permissions %}
{% set canSeeQuantity = 'can_see_quantity' in productPermissions %}
{% endif %}
<div>
{% if alligator_can_see_product_price(productAttrVal.product.permissions) %}
{% set finalPrice = get_price(productAttrVal) %}
{% set actionPrice = get_action_price(productAttrVal) %}
<span class="hidden {{ actionPrice ? 'not' }}" style="text-decoration: line-through;">
{{ finalPrice | alligator_json_convert() | alligator_money_format }}
</span>
{% if actionPrice %}
<span>{{ actionPrice | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</span>
{% else %}
<span>{{ finalPrice | alligator_json_convert() | alligator_money_format }}</span>
{% endif %}
<p class="hidden {{ actionPrice ? 'not' }}" >
{% trans %}You save{% endtrans %}:
<span>{{ (actionPrice ? (finalPrice - actionPrice) : 0) | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</span>
</p>
{% endif %}
</div>
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-page-additional-info', 'product': productAttrVal.product} %}
Product description twig template displays relevant product information in tabs by default.
product-description.html.twig
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-page-full-width'} %}
<div class="col-m-12">
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#description" role="tab" data-toggle="tab" aria-expanded="false">{% trans %}Description{% endtrans %}</a></li>
{% set hasDocuments = product.files | length > 0 %}
{% if hasDocuments %}
<li role="presentation"><a href="#product-documents" role="tab" data-toggle="tab" aria-expanded="false">{% trans %}Documents{% endtrans %}</a></li>
{% endif %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-description-tab-titles'} %}
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="description">
<div>
{% if product.descriptionHtml %}
<p>{{ ('product.'~ product.id ~'.description')|trans({}, 'product-entities') |raw|nl2br }}</p>
{% else %}
{{ 'No description'|trans }}
{% endif %}
{% if product.productFeatures|length %}
<table class="table">
<thead>
<tr>
<th>{% trans %}Feature name{% endtrans %}</th>
<th>{% trans %}Feature value{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% for feature in product.productFeatures %}
<tr>
<td>{{ feature.featureName|trans }}</td>
<td>{{ feature.featureValue|trans }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
{% if hasDocuments %}
<div role="tabpanel" class="tab-pane" id="product-documents">
<div>{% trans %}Documents{% endtrans %}</div>
<div>
{% for file in product.files %}
<li>
{% set docType = (file.name ends with '.doc' or file.name ends with '.docx') ? 'word-o' : (file.name ends with '.pdf' ? 'pdf-o' : 'o') %}
<i class="fa fa-file-{{ docType }}"></i><a href="{{ file.webPath }}" target="_blank">{{ file.name }}</a>
</li>
{% endfor %}
</div>
</div>
{% endif %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'product-description-tab-section'} %}
</div>
</div>
In product-full-width-content.html.twig from the Product Relationship plugin we load products that are in a relationship in the database.
Function that is used:
- get_relationship_names_for_product() gets the defined relationship names from the database
product-full-width-content.html.twig
{% set relationships = get_relationship_names_for_product(product.id) %}
{% if relationships %}
<div class="col-m-12">
<div id="product-relationship-holder">
{% if relationships|length %}
{% for rel in relationships %}
{% if rel.position == 'full_width' %}
<div id="relationship-{{ rel.id }}" class="{{ rel.position }}">
<a href="{{ path('product_relationship_search', {'_locale': app.request.locale, 'relationshipId': rel.id, 'productId': product.id, 'page-title': (rel.name | trans) ~ ' - ' ~ product.productName }) }}">
{{ rel.name|trans }}
</a>
<div>
<div class="loader" style="padding:5px; position: absolute; width: 100%; height: 100%; z-index: 9; padding-top: 100px;background-color: #fbfbfb;"
id="cr-items-loader-{{ rel.id }}">
<img src="{{ asset(get_default_loader()) }}" alt="">
</div>
<ol class="owl-carousel" id="relationship-products-carousel-{{ rel.id }}"
data-relationship="{{ rel.id }}" data-touch-drag="true" data-nav="true" data-dots="false" data-margin="20" data-responsive='{"0":{"items":2},"480":{"items":2},"600":{"items":3},"992":{"items":5}}' data-items="5">
</ol>
<div style="padding:5px;" id="cr-no-products-{{ rel.id }}" hidden>
<p>{% trans %}No related products{% endtrans %}</p>
</div>
</div>
</div>
{% endif %}
{% endfor %}
{% endif %}
</div>
</div>
<script>
var relationshipNames = {{ relationships|json_encode|raw }};
</script>
{% else %}
<script>
var relationshipNames = [];
</script>
{% endif %}
Product description information is loaded from different plugins and in a different sections. The default view is tab view, with tab titles and tab content. The titles and content are loaded separately via layouts. In the main template, without layouts - default product description tab and product files tab (if files exist) are loaded.
First we load comments from the database, and the template for leaving comments. This is a tab title template.
ProductExperiencePlugin/views/Layouts/product-description-tab-titles.html.twig
{% block productDescriptionTabTitles %}
<li role="presentation"><a href="#review-product-section" class="show-comments" role="tab" data-toggle="tab" aria-expanded="false">{% trans %}Comments{% endtrans %}</a></li>
{% endblock %}
This is a tab content template that loads AJAX into the div with the ID.
ProductExperiencePlugin/views/Layouts/product-description-tab-section.html.twig
{% block productDescriptionContent %}
<div role="tabpanel" class="tab-pane" id="review-product-section">
<img src="{{ asset(get_default_loader()) }}" alt="">
</div>
{% endblock %}
This is a comments template that is loaded via AJAX.
ProductExperiencePlugin/views/Layouts/product-description-tab-section-template.html.twig
<div>
{% if is_granted('ROLE_USER') %}
<div id="add-comment">
<div id="product-review-form">
<div class="form-group">
<label for="review_author">{% trans %}Username{% endtrans %}</label>
<input id="review_author" name="review_author" value="{{ get_logged_user().username }}" type="text" class="form-control" readonly/>
</div>
<div class="form-group">
<label for="review_email">{% trans %}Email{% endtrans %}</label>
<input id="review_email" name="review_email" value="{{ get_logged_user().email }}" type="text" class="form-control" readonly/>
</div>
<div class="form-group">
<label for="review_title">{% trans %}Review title{% endtrans %}</label>
<input id="review_title" name="review_title" type="text" class="form-control"/>
</div>
<div class="form-group">
<label for="review_content">{% trans %}Leave a comment{% endtrans %}</label>
<textarea id="review_content" name="review_content" rows="4" class="form-control"></textarea>
</div>
<div class="form-group">
<label for="review_rate_prodct">{% trans %}Rate product{% endtrans %}:</label>
<div id="review_rate_prodct" class="rateit comment-rateit-stars" data-rateit-value="0" data-rateit-ispreset="true" data-rateit-resetable="false"></div>
</div>
<div class="form-group">
<button id="submit_review" class="btn button-border">{% trans %}Submit comment{% endtrans %}</button>
</div>
</div>
</div>
{% else %}
<div style="background-color: #ddd; padding:5px; margin-bottom: 15px;">
{% trans %}In order to leave a comment you need to{% endtrans %}
<a href="{{ path('fos_user_security_login') }}">{% trans %}login{% endtrans %}</a>
/
<a href="{{ path('register') }}">{% trans %}register{% endtrans %}</a>
</div>
{% endif %}
<div class="comments-list">
</div>
<div id="loader" style="background-color: #ddd; padding:5px;">
<img src="{{ asset(get_default_loader()) }}" alt="">
</div>
</div>
After this, Buy X get Y plugins is loaded. It's tab template is loaded directly from the core plugin, but tab section is loaded via theme plugin.
Function that is used:
- alligator_get_buy_x_get_y_offer_for_product(): gets the defined Buy X get Y products from the database
Themes/Starter/Plugins/EmpireBuyXGetYPlugin/views/product-description-tab-section.html.twig
{% set giftProducts = alligator_get_buy_x_get_y_offer_for_product(product.id) %}
{% set isXMLRequest = app.request.headers.get('X-Requested-With') == 'XMLHttpRequest' %}
{% set classAddToCart = 'gift-add-to-cart' %}
{% block productDescriptionContent %}
<div role="tabpanel" class="tab-pane" id="buyxgety-products">
<div class="wrap">
{% if giftProducts|length %}
{% for giftProduct in giftProducts %}
{% set numberInStock = (giftProduct.simpleProduct is defined ? giftProduct.simpleProduct.numberInStock : giftProduct.numberInStock ) %}
{% set canSeePrice = alligator_can_see_product_price(giftProduct.permissions) %}
{% if canSeePrice %}
{% set finalPrice = get_price(giftProduct) %}
{% set actionPrice = get_action_price(giftProduct) %}
{% endif %}
<div class="col-m-3">
<a href="{{path('product_front_slug',{'_locale': locale, 'slug': giftProduct.giftProductSlug})}}">
{% if giftProduct.giftProductImage %}
<img src=" {{ getDefaultExtension(giftProduct.giftProductImage) }}" alt="{{ giftProduct.giftProductName }}">
{% else %}
<img src="{{ asset('Resource/FrontendTheme/images/default_product.png') }}" alt="{{ giftProduct.giftProductName }}">
{% endif %}
</a>
<a href="{{path('product_front_slug',{'_locale': locale, 'slug': giftProduct.giftProductSlug})}}">{{ giftProduct.giftProductName }} </a>
<div class="description">
<div class="simple-article">
</div>
</div>
<div>
{% trans %}Buy{% endtrans %} {{ giftProduct.amount }}
{% if giftProduct.giftProductDiscount == 0 %}
{% trans %}of main product and get this product for{% endtrans %} {{ get_price(giftProduct) | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}
{% elseif giftProduct.giftProductDiscount == 100 %}
{% trans %}of main product and get this product for free{% endtrans %}
{% else %}
{% trans %}of main product and get this product for{% endtrans %} <strong>{{ giftProduct.giftProductDiscount }}</strong>{% trans %}% OFF{% endtrans %}
{% endif %}
</div>
<div>
{{ alligator_get_plugins_for_layout('product-compare-retweet-icon', {'productId': giftProduct.giftProductId, 'index': loop.index0, 'classCompare' : 'gift-compare-class' , 'promo' : 0}) }}
<a class="gift-wishlist"><span class="flaticon-heart"></span></a>
</div>
<a class="{{ classAddToCart }} add-to-cart-{{ giftProduct.giftProductId }}" href="#">
{% trans %}Add To Cart{% endtrans %}
</a>
</div>
<script>
$(document).ready(function(){
$('.pav_change_image_{{ giftProduct.giftProductId }}').click(function(){
var imageSrc= $(this).attr("src");
$('#product_main_image_{{ giftProduct.giftProductId }}').attr('src',imageSrc);
});
});
var productObj = {
id: {{ giftProduct.giftProductId }},
slug: "{{ giftProduct.giftProductSlug }}",
product_name: "{{ giftProduct.giftProductName }}",
{% if canSeePrice %}
current_price : {{ get_price(giftProduct) }},
{% endif %}
simple_product: {
{% if numberInStock != NULL %}
number_in_stock: {{ numberInStock }}
{% endif %}
},
quantity_step: {{ giftProduct.quantityStep ? giftProduct.quantityStep : 1 }},
preventDirectAddToCart: {{ giftProduct.preventDirectAddToCart is defined and giftProduct.preventDirectAddToCart ? 1 : 0 }}
};
var classWishlist = 'gift-wishlist';
var classAddToCart = '{{ classAddToCart }}';
var index = {{ loop.index0 }};
var customBtn = new AddToCartButton( $('.demo-product-item .preview-buttons .'+classAddToCart)[index], productObj);
{{ giftProduct.permissions is defined and alligator_check_product_add_to_cart_permission(giftProduct.permissions) == false ? 'customBtn.setNotAllowed();' }}
allAddToCartButtons.push({id: productObj.id, btn: customBtn});
var wishBtn = new WishlistButton($('.demo-product-item .icons .'+classWishlist)[index], productObj.id);
// remove this later
{% if giftProduct.preventDirectAddToCart is not defined %}
console.log('Product object does not have all fields needed. Check preventDirectAddToCart field', productObj);
{% endif %}
</script>
{% endfor %}
{% else %}
<p>{% trans %}No discount products!{% endtrans %}</p>
{% endif %}
</div>
</div>
{% endblock %}
Entity relationship plugin is loaded directly from the core plugin into the tab title and tab content.
Product video plugin is loaded last, and its templates are within our Starter theme.
video-tab-title.html.twig
<li role="presentation"><a href="#product-video-section" role="tab" data-toggle="tab" aria-expanded="false">{% trans %}Video{% endtrans %}</a></li>
The actual video is loaded in the tab content template.
Functions that are used:
- alligator_product_video(): gets product videos from the database
- alligator_format_for_youtube(): formats the video URL for youtube
- alligator_format_for_vimeo(): formats the video URL for vimeo
video-tab-section.html.twig
<div role="tabpanel" class="tab-pane" id="product-video-section">
{% set videos = alligator_product_video(product.id) %}
{% block productVideoPreview %}
<div>
{% if videos|length%}
{% for video in videos %}
<div class="wrap">
{% if video.videoFilePath %}
<div class="col-m-6">
<video width="360" controls>
<source src="{{ video.videoFilePath }}" type="video/mp4"/>
</video>
</div>
{% elseif video.youtube %}
<div class="col-m-6">
<iframe width="560" height="315" src="{{ alligator_format_for_youtube(video.youtube) }}" frameborder="0" allowfullscreen></iframe>
</div>
{% elseif video.vimeo %}
<div class="col-m-6">
<iframe width="560" height="315" src="{{ alligator_format_for_vimeo(video.vimeo) }}" frameborder="0" allowfullscreen></iframe>
</div>
{% endif %}
<div class="col-m-6">
<h5>{{ video.title }}</h5>
<p>{{ video.description|raw }}</p>
</div>
</div>
</div>
{% endfor %}
{% else %}
<p>{% trans %} No videos {% endtrans %}</p>
{% endif %}
{% endblock %}
</div>
User profile
User profile provides essential pages for user management and overview of profile information, orders, tickets and coupons. All the templates are within User plugin.
Main template that is in the core is user-dashboard.html.twig. All of child templates are comprised of side-menu template, and main content templates.
Side menu
This is a menu template, it lists all available pages with links.
side-menu.html.twig
{% set currentRoute = app.request.get('_route') %}
<div>
<div>
<div class="user-profile-img">
{% if user.baseImageUrl %}
<img id="user-profile-img" src="{{ user.baseImageUrl }}" class="img-responsive" alt="User profile image">
{% else %}
<img id="user-profile-img" src="{{ asset('Resource/FrontendTheme/images/avatar.jpg') }}" class="img-responsive" alt="User profile image">
{% endif %}
{% if currentRoute == 'user_profile' %}
<button id="new-user-image">
</button>
{% endif %}
</div>
<div> {{ user.firstName }} {{ user.lastName }}</div>
<div> {{ user.username }} </div>
<a href="{{ path('checkout_summary', {'_locale':locale}) }}" class="btn btn-primary" type="button">{% trans %}Shop now{% endtrans %}</a>
</div>
<ul class="nav">
<li class="{% if currentRoute == 'user_profile' %}active{% endif %}">
<a href="{{ path('user_profile') }}" class="{% if currentRoute == 'user_profile' %}current-user-page{% endif %}">
{% trans %}Profile{% endtrans %}</a>
</li>
<li class="{% if currentRoute == 'user_address' %}active{% endif %}">
<a href="{{ path('user_address') }}" class=" {% if currentRoute == 'user_address' %}current-user-page{% endif %}">
{% trans %}Address{% endtrans %}</a>
</li>
<li class="{% if currentRoute == 'user_orders' %}active{% endif %}">
<a href="{{ path('user_orders') }}" class=" {% if currentRoute == 'user_orders' %}current-user-page{% endif %}">
{% trans %}Orders{% endtrans %}</a>
</li>
<li class="{% if currentRoute == 'user_tickets' %}active{% endif %}">
<a href="{{ path('user_tickets') }}" class="{% if currentRoute == 'user_tickets' %}current-user-page{% endif %}">
{% trans %}Tickets{% endtrans %}</a>
</li>
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'user-side-menu-additional-information'} %}
<li class="{% if currentRoute == 'user_wishlist' %}active{% endif %}">
<a href="{{ path('user_wishlist') }}" class="{% if currentRoute == 'user_wishlist' %}current-user-page{% endif %}">
{% trans %}Wishlist{% endtrans %}</a>
</li>
</ul>
</div>
User Profile
This is the main user profile template which includes child content template and styles. Also, a modal for image cropping is included in this template.
user-profile.html.twig
{% extends '@userPluginViews/User/user-profile.html.twig' %}
{% set extendsFromTheme = true %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('Resource/Plugins/EmpireUserPlugin/css/user_profile_frontend_compiled.css') }}">
{% endblock %}
{% block mainContent %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireUserPlugin/views/User/user-profile-content.html.twig' %}
<div class="modal" id="modal" role="dialog" aria-labelledby="modalLabel" tabindex="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalLabel">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</h5>
</div>
<div class="modal-body">
<div class="img-container">
<img id="avatar-crop" class="user-profile-img-class" src="{{ asset('Resource/FrontendTheme/images/avatar.jpg') }}" alt="Picture">
</div>
</div>
<div class="modal-footer">
<button type="button" id="crop-avatar" class="btn button-border" data-dismiss="modal">{% trans %}Save{% endtrans %}</button>
</div>
</div>
</div>
</div>
{% endblock mainContent %}
User profile content contains all information about the user and the option to change/edit that information. Also it includes scripts for these functionalities.
Functions that are used:
- get_active_checkout_steps(): gets the active checkout steps from the database
- getPossibleShippingProviders(): gets available shipping providers from the database
- getCurrentPaymentTypes(): gets current payment types from the database
user-profile-content.html.twig
{% set facebookLink, twitterLink, gPlusLink = user.facebookLink, user.twitterLink, user.gPlusLink %}
<div class="col-m-9">
<h2>{% trans %}General information{% endtrans %}</h2>
<div class="wrap">
<div class="col-m-6">
<div class="form-group">
<label for="user_code">{% trans %}Full Name{% endtrans %}</label>
<p>{{ user.firstName ~ ' ' ~ user.lastName}} </p>
</div>
<div class="form-group">
<label for="user_code">{% trans %}E-Mail{% endtrans %}</label>
<p>{{ user.email}}</p>
</div>
</div>
<div class="col-m-6">
<div class="form-group">
<label for="user_code">{% trans %}Username{% endtrans %}</label>
<p>{{ user.username}}</p>
</div>
<div class="form-group">
<label for="user_code">{% trans %}Password{% endtrans %}</label>
<a title="{% trans %}Change password{% endtrans %}" href="{{ path('changeInModal') }}">
{% trans %}Change password{% endtrans %}
</a>
</div>
</div>
</div>
<div id="profile-information">
<h3>{% trans %}Edit Your Profile Information Below{% endtrans %}</h3>
<div id="edit-profile-details">
<div class="wrap">
<div class="col-m-6">
<div class="form-group">
<label for="avatar">{% trans %}Avatar{% endtrans %}</label>
<form class="upload-drop-zone avatar-img" id="avatar"></form>
</div>
</div>
<div class="col-m-12">
<form role="form" id="edit-user-profile-form" autocomplete="off" class="wrap">
<div class="col-m-6">
{% if userSettings.requireERPCode %}
<div class="form-group">
<label for="user_code">{% trans %}User code{% endtrans %}</label>
<input type="text" class="form-control" required value="{{ user.userCode }}" name="user_code" disabled>
</div>
{% endif %}
<div class="form-group">
<label for="first_name">{% trans %}First name{% endtrans %}</label>
<input type="text" class="form-control" value="{{ user.firstName}}" name="first_name" required>
</div>
<div class="form-group">
<label for="username">{% trans %}Username{% endtrans %}</label>
<input type="text" class="form-control" value="{{ user.username}}" name="username">
</div>
{% if user.phoneNumbers |length > 1 %}
<div class="form-group">
<label for="phone">{% trans %}Phone{% endtrans %}</label>
<input type="text" class="form-control" value="{{ user.phoneNumbers[1]['phone_number'] }}" name="phone">
</div>
{% else %}
<div class="form-group">
<label for="phone">{% trans %}Phone{% endtrans %}</label>
<input type="text" class="form-control" value="" name="phone">
</div>
{% endif %}
<div class="form-group">
<label for="google_plus_url">{% trans %}Google Plus URL{% endtrans %}</label>
{% if gPlusLink is null or gPlusLink=='' %}
<input type="text" class="form-control" name="g_plus_link">
{% else %}
<input type="text" class="form-control" name="g_plus_link" value="{{ gPlusLink }}">
{% endif %}
</div>
</div>
<div class="col-m-6">
<div class="form-group">
<label for="last_name">{% trans %}Last name{% endtrans %}</label>
<input type="text" class="form-control" value="{{ user.lastName }}" name="last_name">
</div>
{% if user.phoneNumbers |length > 0 %}
<div class="form-group">
<label for="mobile_phone">{% trans %}Mobile Phone{% endtrans %}</label>
<input type="text" class="form-control" value="{{ user.phoneNumbers[0]['mobile_phone'] }}" name="mobile_phone">
</div>
{% else %}
<div class="form-group">
<label for="mobile_phone">{% trans %}Mobile Phone{% endtrans %}</label>
<input type="text" class="form-control" value="" name="mobile_phone">
</div>
{% endif %}
<div class="form-group">
<label for="facebook_url">{% trans %}Facebook URL{% endtrans %}</label>
{% if facebookLink is null or facebookLink=='' %}
<input type="text" class="form-control" name="facebook_link">
{% else %}
<input type="text" class="form-control" name="facebook_link" value="{{ facebookLink }}">
{% endif %}
</div>
<div class="form-group">
<label for="twitter_url">{% trans %}Twitter URL{% endtrans %}</label>
{% if twitterLink is null or twitterLink=='' %}
<input type="text" class="form-control" name="twitter_link">
{% else %}
<input type="text" class="form-control" name="twitter_link" value="{{ twitterLink }}">
{% endif %}
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% set possibleSteps = get_active_checkout_steps() %}
{% set allShippingProviders = getPossibleShippingProviders() %}
{% set allPaymentTypes = getCurrentPaymentTypes() %}
{% if constant('Alligator\\Model\\Core\\CheckoutSettings\\Model\\PossibleStatesExtension::STEP_3') in possibleSteps or constant('Alligator\\Model\\Core\\CheckoutSettings\\Model\\PossibleStatesExtension::STEP_4') in possibleSteps %}
<div>
<h3>{% trans %}Edit Your Default Shipping and Payment Type{% endtrans %}</h3>
<form>
{% if constant('Alligator\\Model\\Core\\CheckoutSettings\\Model\\PossibleStatesExtension::STEP_3') in possibleSteps %}
<div class="form-group">
<label for="shipping-select">{% trans %}Default Shipping{% endtrans %}</label>
<select class="form-control" name="shipping-select" id="shipping-select">
<option></option>
{% for shippingProvider in allShippingProviders %}
<option value="{{ shippingProvider.id }}" {{ user.defaultShippingProvider and user.defaultShippingProvider.id == shippingProvider.id ? 'selected' }}>{{ shippingProvider.name|trans }}</option>
{% endfor %}
</select>
</div>
{% endif %}
{% if constant('Alligator\\Model\\Core\\CheckoutSettings\\Model\\PossibleStatesExtension::STEP_4') in possibleSteps %}
<div class="form-group">
<label for="payment-select">{% trans %}Default Payment{% endtrans %}</label>
<select class="form-control" name="payment-select" id="payment-select">
<option></option>
{% for paymentName, paymentType in allPaymentTypes %}
<option value="{{ paymentType['value'] }}" {{ user.defaultPaymentType == paymentType['value'] ? 'selected' }}>{{ paymentName|trans }}</option>
{% endfor %}
</select>
</div>
{% endif %}
</form>
</div>
{% endif %}
<button type="button" id="submit-user-profile-form-btn" class="btn btn-primary">{% trans %}SAVE CHANGES{% endtrans %}</button>
</div>
<script src="{{ asset ('Resource/Plugins/EmpireUserPlugin/js/user_profile_content_compiled.js') }}"></script>
Address
This template serves as a address management page. Users can edit address data and add new or change existing addresses.
user-address.html.twig
{% extends '@userPluginViews/User/user-address.html.twig' %}
{% set extendsFromTheme = true %}
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('Resource/Plugins/EmpireUserPlugin/css/user_profile_dashboard_compiled.css') }}">
{% endblock %}
{% block mainContent %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireUserPlugin/views/User/user-address-content.html.twig' %}
{% endblock mainContent %}
user-address-content.html.twig is the main child template where all the forms are placed. Also, a shipping address modal is included where users can add new addresses.
user-address-content.html.twig
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireUserPlugin/views/Layout/addShippingAddressModal.html.twig' %}
<div class="col-m-9">
<h2>{% trans %}Address Settings{% endtrans %}</h2>
<label for="address">{% trans %}Edit Your Billing Address Information Below{% endtrans %}</label>
<form role="form" id="edit-address-details-form" autocomplete="off">
{% set address = userAddresses.address %}
<div class="wrap">
<div class="col-m-6">
<div class="form-group">
<label for="email">{% trans %}Email{% endtrans %}</label>
<input type="text" class="form-control" placeholder="{% trans %}Email{% endtrans %}" id="email_edt" name="email" value="{{ address.email }}">
</div>
<div class="form-group">
<label for="street">{% trans %}Street{% endtrans %}</label>
<input type="text" name="street" class="field field form-control" id="route_edt" placeholder="{% trans %}Street{% endtrans %}" value="{{ address.street }}">
</div>
<div class="form-group">
<label for="city">{% trans %}City{% endtrans %}</label>
<input type="text" class="form-control" placeholder="{% trans %}City{% endtrans %}" id="usrCity_edt" name="city" value="{{ address.city }}">
</div>
</div>
<div class="col-m-6">
<div class="form-group">
<label for="country">{% trans %}Country{% endtrans %}</label>
<input type="text" class="form-control" placeholder="{% trans %}Country{% endtrans %}" id="usrCountry_edt" name="country" value="{{ address.country }}">
</div>
<div class="form-group">
<label for="street_number">{% trans %}Street number{% endtrans %}</label>
<input type="text" name="street_number" class="form-control" id="street_number_edt" placeholder="{% trans %}Street number{% endtrans %}" value="{{ address.streetNumber }}">
</div>
</div>
<div class="col-m-12">
<button type="button" id="submit-address-form-btn" class="btn btn-success">{% trans %}Save{% endtrans %}</button>
</div>
</div>
</form>
<div id="edit-address-details">
<div class="form-group" id="shipping-addresses-container-dnt">
<label for="shipping" class="label-title-user">{% trans %}Shipping Address{% endtrans %}</label>
{% for address in userAddresses.shippingAddresses %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireUserPlugin/views/User/address-template-component.html.twig' %}
{% endfor %}
</div>
<div class="form-group">
<button class="btn btn-primary" id="add-new-shipping-address" data-toggle="modal" data-target="#shippingAddressModal">{% trans %}Add new shipping address{% endtrans %}</button>
</div>
</div>
</div>
addShippingAddressModal.html.twig
{% block addressMainModal %}
{% block addAddressModal %}
<div class="modal fade" id="shippingAddressModal" tabindex="-1" role="dialog"
aria-labelledby="shippingAddressModal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="myModalLabel">{% trans %}Add shipping address{% endtrans %}
<button type="button" class="pull-right close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true"><i class="fa fa-close"></i></span>
</button>
</h4>
</div>
<div class="modal-body">
<form role="form" id="address-form">
<div class="form-group">
<label>{% trans %}Email address{% endtrans %}</label>
<input type="text" class="form-control" name="emailAdd" id="emailAdd" autocomplete="off">
</div>
<div class="form-group">
<label>{% trans %}Please enter your shipping address below{% endtrans %}</label>
<input type="text" class="form-control" name="address" id="autocomplete"
onfocus="geolocate()" autocomplete="off"
placeholder="{% trans %}Enter a location{% endtrans %}">
<label for="autocomplete"></label>
</div>
<div class="form-group">
<label for="route">{% trans %}Street {% endtrans %}</label>
<input type="text" name="street" class="form-control" id="route" disabled="true">
</div>
<div class="form-group">
<label for="street_number">{% trans %}Street number{% endtrans %}</label>
<input type="text" name="street_number_order" class="form-control" id="street_number" disabled="true">
</div>
<div class="form-group">
<label for="country">{% trans %}Country{% endtrans %}</label>
<input type="text" id="country" class="form-control" name="country" required disabled="true">
</div>
<div class="form-group">
<label for="locality">{% trans %}City{% endtrans %}</label>
<input type="text" name="city" class="form-control" id="locality" disabled="true">
</div>
<div class="form-group">
<label for="postal_code">{% trans %}ZIP{% endtrans %}</label>
<input type="text" name="postcode_order" class="form-control"
id="postal_code" disabled="true">
</div>
<div class="form-group">
<label for="phone_number">{% trans %}Phone number{% endtrans %}</label>
<input type="text" name="phone_number" class="form-control" id="phone_number">
</div>
<div class="form-group">
<a class="btn btn-primary" id='address-save' href="#">{% trans %}SAVE{% endtrans %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock addAddressModal %}
{% block editAddressModal %}
<div class="modal fade" id="shippingAddressModalEdit" tabindex="-1" role="dialog"
aria-labelledby="shippingAddressModalEdit" data-address-id="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="myModalLabel">{% trans %}Edit shipping address{% endtrans %}
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true"><i class="fa fa-close"></i></span>
</button>
</h4>
</div>
<div class="modal-body">
<form role="form" id="address-form">
<div class="form-group">
<label>{% trans %}Email address{% endtrans %}</label>
<input type="text" class="form-control" name="emailEdit" id="emailEdit" autocomplete="off">
</div>
<div class="form-group">
<label>{% trans %}Please enter your billing address below{% endtrans %}</label>
<input type="text" class="form-control" name="address" id="autocompleteEdit"
onfocus="geolocateEdit()" autocomplete="off"
placeholder="{% trans %}Enter a location{% endtrans %}">
<label for="autocomplete"></label>
</div>
<div class="form-group">
<label for="street_number">{% trans %}Street number{% endtrans %}</label>
<input type="text" name="street_number_order" class="form-control" id="street_number_edit">
</div>
<div class="form-group">
<label for="route">{% trans %}Street {% endtrans %}</label>
<input type="text" name="street" class="form-control" id="route_edit">
</div>
<div class="form-group">
<label for="country">{% trans %}Country{% endtrans %}</label>
<input type="text" id="country_edit" class="form-control" name="country_edit" required>
</div>
<div class="form-group">
<label for="locality">{% trans %}City{% endtrans %}</label>
<input type="text" name="city" class="form-control" id="locality_edit">
</div>
<div class="form-group">
<label for="postal_code">{% trans %}ZIP{% endtrans %}</label>
<input type="text" name="postcode_order" class="form-control" id="postal_code_edit">
</div>
<div class="form-group">
<label for="phone_number">{% trans %}Phone number{% endtrans %}</label>
<input type="text" name="phone_number_edit" class="form-control" id="phone_number_edit">
</div>
<div class="form-group">
<a class="btn btn-primary" id='address-edit'
href="#">{% trans %}SAVE{% endtrans %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock editAddressModal %}
{% block addressScripts %}
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=AIzaSyCPURcNo9iGaHTvam7DO4lrN7hLkTutaPA&libraries=places"></script>
<script src="{{ asset('Resource/Plugins/EmpireUserPlugin/js/user_address_add_modal_content_compiled.js') }}"></script>
{% endblock addressScripts %}
{% endblock %}
This small template contains HTML for address list with options for editing and removal.
address-template-component.html.twig
{% if order is defined and order.address is not null and address is not null %}
{% if order.address.id == address.id %}
<div class="shipping-address-holder-dnt alert alert-info" data-id="{{ address.id }}">
<p class="select-trigger-dnt selected"><span>{{ address.street }} {{ address.streetNumber }}, {{ address.postcode }} {{ address.city }} {{ address.country }}</span></p>
<div class="success-address-div">
<i class="fa fa-close remove-address-dnt"></i>
<i class="fa fa-pencil edit-address-dnt"></i>
</div>
</div>
{% else %}
<div class="shipping-address-holder-dnt alert alert-info" data-id="{{ address.id }}">
<p class="select-trigger-dnt"><span>{{ address.street }} {{ address.streetNumber }}, {{ address.postcode }} {{ address.city }} {{ address.country }}</span></p>
<div class="success-address-div">
<i class="fa fa-close remove-address-dnt"></i>
<i class="fa fa-pencil edit-address-dnt"></i>
</div>
</div>
{% endif %}
{% else %}
<div class="shipping-address-holder-dnt alert alert-info" data-id="{{ address.id }}">
<p class="select-trigger-dnt"><span>{{ address.street }} {{ address.streetNumber }}, {{ address.postcode }} {{ address.city }} {{ address.country }}</span></p>
<div class="success-address-div">
<i class="fa fa-close remove-address-dnt"></i>
<i class="fa fa-pencil edit-address-dnt"></i>
</div>
</div>
{% endif %}
Orders
This is a template that displays user's order history. It lists all of the user's orders with the information such as ID, date, status, details and the option Quick order which repeats the same order. Scripts are loaded in the end.
user-order-history.html.twig
{% extends '@userPluginViews/User/user-order-history.html.twig' %}
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('Resource/Plugins/EmpireUserPlugin/css/user_profile_dashboard_compiled.css') }}">
{% endblock %}
{% block mainContent %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireUserPlugin/views/User/user-order-history-content.html.twig' %}
{% endblock %}
user-order-history-content.html.twig
{% block userOrdersHistoryMainContent %}
<div class="col-m-9">
<h2>{% trans %}Order History{% endtrans %}</h2>
{% if orders %}
<div class="table-responsive">
<table class="table table-striped" id="user-orders-container-dnt">
<thead>
<tr>
<td>{% trans %}Order ID{% endtrans %}</td>
<td>{% trans %}Date{% endtrans %}</td>
<td>{% trans %}Status{% endtrans %}</td>
<td>{% trans %}Details{% endtrans %}</td>
<td>{% trans %}Quick order{% endtrans %}</td>
</tr>
</thead>
<tbody id="user-orders-table">
{% for order in orders %}
<tr>
<td># {{ order.id }}</td>
<td>{{ (order.completedAt ? order.completedAt : order.createdAt) |date('d.m.Y') }}</td>
<td>
<div class="user-order-state">
{% if order.state == 'delivered' %}
<i class="fa fa-circle green"></i>
{% else %}
<i class="fa fa-circle red"></i>
{% endif %}
{{ order.state|trans }}
</div>
</td>
<td>
<a class="btn-icon-edit" href="{{ path('user_orders_details', {'id': order.id, '_locale': app.request.get('_locale') }) }}">
{% trans %}View{% endtrans %}
</a>
</td>
<td>
<a class="btn-icon-edit btn-quick-order" href="{{ path('user_quick_reorder', {'id': order.id}) }}">
{% trans %}Quick order{% endtrans %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p>{% trans %}No orders have been made yet!{% endtrans %}</p>
{% endif %}
{% if countOrders > 10 %}
<nav>
<ul class="uk-pagination outer">
<li><a href="" id="pagination-first-page"><i class="fa fa-angle-double-left" ></i></a></li>
<li><a href="" id="pagination-previous-page"><i class="fa fa-angle-left" ></i></a></li>
<li>
<ul class="uk-pagination" id="user-orders-pagination-num-dnt">
</ul>
</li>
<li><a href="" id="pagination-next-page"><i class="fa fa-angle-right"></i></a></li>
<li><a href="" id="pagination-last-page"><i class="fa fa-angle-double-right"></i></a></li>
</ul>
</nav>
{% endif %}
</div>
{% endblock userOrdersHistoryMainContent %}
{% block userOrdersHistoryScripts %}
<script>
var ordersCount = {{ countOrders }};
</script>
<script src="{{ asset ('Resource/Plugins/EmpireUserPlugin/js/user_orders_pagination_compiled.js') }}"></script>
{% endblock userOrdersHistoryScripts %}
This is the orders table template for UK pagination that is loaded when user switches to the next page.
user-orders-content-template.html.twig
{% if orders %}
<table class="table" id="user-orders-container-dnt">
<thead>
<tr>
<td>{% trans %}Order ID{% endtrans %}</td>
<td>{% trans %}Date{% endtrans %}</td>
<td>{% trans %}Status{% endtrans %}</td>
<td>{% trans %}Details{% endtrans %}</td>
<td>{% trans %}Quick order{% endtrans %}</td>
</tr>
</thead>
<tbody id="user-orders-table">
{% for order in orders %}
<tr>
<td># {{ order.id }}</td>
<td>{{ (order.completedAt ? order.completedAt : order.createdAt) |date('d.m.Y') }}</td>
<td>
{% if order.state == 'delivered' %}
<i class="fa fa-circle green"></i>
{% else %}
<i class="fa fa-circle red"></i>
{% endif %}
{{ order.state|trans }}
</td>
<td>
<a class="btn-icon-edit" data-toggle="tooltip" title="{% trans %}Details{% endtrans %}" href="{{ path('user_orders_details', {'id': order.id, '_locale': app.request.get('_locale') }) }}">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 59.2 59.2" style="enable-background:new 0 0 59.2 59.2;" xml:space="preserve"> <g> <path d="M51.062,21.561c-11.889-11.889-31.232-11.889-43.121,0L0,29.501l8.138,8.138c5.944,5.944,13.752,8.917,21.561,8.917 s15.616-2.972,21.561-8.917l7.941-7.941L51.062,21.561z M49.845,36.225c-11.109,11.108-29.184,11.108-40.293,0l-6.724-6.724 l6.527-6.527c11.109-11.108,29.184-11.108,40.293,0l6.724,6.724L49.845,36.225z"/> <path d="M28.572,21.57c-3.86,0-7,3.14-7,7c0,0.552,0.448,1,1,1s1-0.448,1-1c0-2.757,2.243-5,5-5c0.552,0,1-0.448,1-1 S29.125,21.57,28.572,21.57z"/> <path d="M29.572,16.57c-7.168,0-13,5.832-13,13s5.832,13,13,13s13-5.832,13-13S36.741,16.57,29.572,16.57z M29.572,40.57 c-6.065,0-11-4.935-11-11s4.935-11,11-11s11,4.935,11,11S35.638,40.57,29.572,40.57z"/> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> <g> </g> </svg>
</a>
</td>
{% set customQuickOrderUserProfile = count_plugins_for_layout('custom-quick-order-user-profile') %}
{% if customQuickOrderUserProfile %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'custom-quick-order-user-profile'} %}
{% else %}
<td>
<a class="btn-icon-edit btn-quick-order" data-toggle="tooltip" title="{% trans %}Quick order{% endtrans %}" href="{{ path('user_quick_reorder', {'id': order.id}) }}">
<i class="fa fa-bars" aria-hidden="true"></i>
</a>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
These templates display order information in a more detailed way.
user-order-details.html.twig
{% extends '@userPluginViews/User/user-profile.html.twig' %}
{% set extendsFromTheme = true %}
{% set user = get_logged_user() %}
{% block mainContent %}
<div class="modal fade" id="bundleProductModal" tabindex="-1" role="dialog" aria-labelledby="modalExceptionLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">x</button>
<h4 id="bundleProductModalTitle" class="modal-title"></h4>
</div>
<div class="modal-body">
<div id="bundleProductModalBody"></div>
<div class="modal-footer">
<button tabindex="2" class="btn btn-md btn-fill btn-red" id="bundle-modal-cancel" data-dismiss="modal" aria-label="Close" >{% trans %}CLOSE{% endtrans %}</button>
</div>
</div>
</div>
</div>
</div>
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireUserPlugin/views/User/user-order-details-content.html.twig' %}
{% endblock mainContent %}
{% block custom %}
{{ parent() }}
<script>
var tmpGroupProductsArray = null;
var orderId = {{ order.id }};
$(document).on('click', '.open-bundle-modal-dnt', function (evt) {
evt.preventDefault();
function successGroupProductModal(ctx, data) {
tmpGroupProductsArray = data.data;
populateProductBundleModal($(ctx).data('productname'), tmpGroupProductsArray[$(ctx).data('groupid')]);
}
if(tmpGroupProductsArray) {
populateProductBundleModal($(this).data('productname'), tmpGroupProductsArray[$(this).data('groupid')]);
} else {
adapter.sendData(Routing.generate('get_all_group_products_for_order', {_locale: getURLParameter('locale')}, true),
{ orderId: orderId }, "POST", successGroupProductModal, $(this));
}
function populateProductBundleModal(title, html) {
$('#bundleProductModalTitle').html(title);
$('#bundleProductModalBody').html(html);
$('#bundleProductModal').modal('show');
}
});
</script>
{% endblock %}
user-order-details-content.html.twig
<div class="col-m-9">
<h2>{% trans %}Order Details{% endtrans %}</h2>
<a class="btn btn-primary" href="{{ path('user_quick_reorder', {'id': order.id}) }}" target="_blank">
{% trans %}Quick order{% endtrans %}
</a>
<a class="btn btn-primary" href="{{ path('user_order_print', {'_locale': app.request.get('_locale'), 'id': order.id}) }}" target="_blank" >
{% trans %}Print{% endtrans %}
</a>
<div class="table-responsive" id="order-details-table-holder">
<table class="table table-striped">
<thead>
<tr>
<th>{% trans %}Image{% endtrans %}</th>
<th>{% trans %}Product name{% endtrans %}</th>
<th>{% trans %}Price with vat{% endtrans %}</th>
<th>{% trans %}Quantity{% endtrans %}</th>
<th>{% trans %}Subtotal with vat{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% set sumOfAllAdjustmentsTotal = 0 %}
{% if order is not null %}
{% set items = order.items %}
{% set qty=0 %}
{% for item in items %}
{% set sumOfAllAdjustmentsTotal = sumOfAllAdjustmentsTotal + ((item.adjustmentsTotal | abs) / 100 * item.getUnitPrice * item.quantity) %}
<tr>
<td>
<a href="{{ path('product_front_slug', {'slug': item.product.getSlug} ) }}" data-toggle="tooltip" title="{% trans %}Product SKU{% endtrans %}: {{ item.product.sku }}">
{% if item.product.baseImageUrl %}
<img src="{{ getDefaultExtension(item.product.baseImageUrl) }}" alt="{{ item.product.getProductName }}" class="img-responsive">
{% else %}
<img src="{{ asset('Resource/FrontendTheme/images/default_product.png') }}" alt="{{ item.product.getProductName }}" class="img-responsive">
{% endif %}
</a>
{% if item.product.groupProduct %}
<a href="#" class="open-bundle-modal-dnt" data-productname="{{ item.product.productName }}" data-groupid="{{ item.product.groupProduct.id }}">{% trans %}See bundle product{% endtrans %}</a>
{% endif %}
</td>
<td>
{% if item.product %}
<a href="{{ path('product_front_slug', {'slug': item.product.getSlug} ) }}">
{{ ('product.' ~ item.product.id ~ '.name') | trans({}, 'product-entities') }}
</a>
{% else %}
<a href="#">{{ item.getProductName }}</a>
{% endif %}
</td>
<td>{{item.unitPrice | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</td>
<td > {{ item.getQuantity}}</td>
{% set qty = qty + item.getQuantity %}
<td>{{item.getTotal | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
<table class="table">
<tbody>
<tr>
<td>{% trans %}Subtotal without discount{% endtrans %}</td>
<td>{{ (order.getTotalWithTax + sumOfAllAdjustmentsTotal) | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</td>
</tr>
<tr>
<td>{% trans %}Discount{% endtrans %}</td>
<td>{{ sumOfAllAdjustmentsTotal | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</td>
</tr>
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'order-details-user-information'} %}
<tr>
<td id="grand-total-price">{% trans %}Grand Total Price{% endtrans %}</td>
<td id="">{{ order.getTotalWithTax | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</td>
</tr>
</tbody>
</table>
</div>
</div>
Tickets
TIcketing system of F-Webshop displays on the user profile. User can view a list of submitted tickets, create new tickets and comment on existing ones. There are three main templates for tickets:
- user-tickets.html.twig: lists all existing tickets
- create-ticket.html.twig: ticket creation page
- user-single-ticket.html.twig: existing ticket edit and overview
user-tickets.html.twig
{% extends '@userPluginViews/User/user-tickets.html.twig' %}
{% set extendsFromTheme = true %}
{% block mainContent %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireUserPlugin/views/User/user-tickets-content.html.twig' %}
{% endblock %}
user-tickets-content.html.twig
{% set currentRoute = app.request.get('_route') %}
{% block ticketsMainContent %}
<div class="col-m-9">
<h2>{% trans %}Ticket History{% endtrans %}</h2>
<a href="{{ path('user_add_ticket') }}" class="{% if currentRoute == 'user_add_ticket' %}current-user-page{% endif %} btn btn-primary">
{% trans %}Create Ticket{% endtrans %}
</a>
<div id="user-tickets-container-dnt">
{% if tickets|length %}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr class="tickets-row">
<td>{% trans %}Ticket ID{% endtrans %}</td>
<td>{% trans %}Title{% endtrans %}</td>
<td>{% trans %}Date{% endtrans %}</td>
<td>{% trans %}Details{% endtrans %}</td>
</tr>
</thead>
<tbody id="user-tickets-table">
{% for ticket in tickets %}
<tr>
<td>{{ ticket.id }}</td>
<td>{{ ticket.name }}</td>
<td>{{ ticket.createdAt|date('d.m.Y') }}</td>
<td>
<a class="btn-icon-edit" href="{{ path('ticket_details', {'id': ticket.id, '_locale': app.request.get('_locale') }) }}">
{% trans %}View{% endtrans %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>{% trans %}No tickets created{% endtrans %}</p>
{% endif %}
</div>
</div>
</div>
{% if countTickets > 5 %}
<nav>
<ul class="uk-pagination outer">
<li><a href="" id="pagination-first-page"><i class="fa fa-angle-double-left" ></i></a></li>
<li><a href="" id="pagination-previous-page"><i class="fa fa-angle-left" ></i></a></li>
<li>
<ul class="uk-pagination" id="user-tickets-pagination-num-dnt">
</ul>
</li>
<li><a href="" id="pagination-next-page"><i class="fa fa-angle-right"></i></a></li>
<li><a href="" id="pagination-last-page"><i class="fa fa-angle-double-right"></i></a></li>
</ul>
</nav>
{% endif %}
{% endblock ticketsMainContent %}
{% block ticketsScripts %}
<script>
var ticketsCount = {{ countTickets }};
</script>
<script src="{{ asset ('Resource/Plugins/EmpireUserPlugin/js/user_tickets_content_scripts_compiled.js') }}"></script>
{% endblock ticketsScripts %}
user-tickets-content-template.html.twig
<table class="table table-striped">
<thead>
<tr class="tickets-row">
<td>{% trans %}Ticket ID{% endtrans %}</td>
<td>{% trans %}Title{% endtrans %}</td>
<td>{% trans %}Date{% endtrans %}</td>
<td>{% trans %}Details{% endtrans %}</td>
</tr>
</thead>
<tbody id="user-tickets-table">
{% for ticket in tickets %}
<tr>
<td>{{ ticket.id }}</td>
<td>{{ ticket.name }}</td>
<td>{{ ticket.createdAt|date('d.m.Y') }}</td>
<td>
<a class="btn-icon-edit" data-toggle="tooltip" title="{% trans %}Details{% endtrans %}" href="{{ path('ticket_details', {'id': ticket.id, '_locale': app.request.get('_locale') }) }}">
<span class="fa fa-eye details-icon" aria-hidden="true"></span>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<nav>
<ul class="uk-pagination outer">
<li>
<ul class="uk-pagination" id="user-tickets-pagination-num-dnt">
</ul>
</li>
</ul>
</nav>
create-ticket.html.twig
{% extends '@userPluginViews/User/user-dashboard.html.twig' %}
{% set extendsFromTheme = true %}
{% set user = get_logged_user() %}
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('Resource/Plugins/EmpireUserPlugin/js/create_ticket_styles_compiled.css') }}">
{% endblock %}
{% block mainContent %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireUserPlugin/views/User/create-ticket-content.html.twig' %}
{% endblock %}
{% block custom %}
<script>
var selectedProductId = "{{ product ? product.id : "undefined" }}";
var selectedProductName = "{{ product ? product.productName : "undefined" }}";
</script>
<script src="{{ asset ('Resource/Plugins/EmpireUserPlugin/js/create_ticket_scripts_compiled.js') }}"></script>
<script>
var userName = {{ user.username |json_encode|raw }};
userId = {{ user.id |json_encode|raw }};
$('.edit-icon').on('click', function(){
$('#edit-profile-details').toggle();
})
</script>
<script>
$('#ticket_type').select2();
</script>
{% endblock %}
Function that is used:
- get_all_possible_ticket_types(): gets all existing ticket types from the database
create-ticket-content.html.twig
<div class="col-m-9">
<h2>{% trans %}Need help? This is a place for you!{% endtrans %}</h2>
<h4>{% trans %}Our ticketing system helps you with various issues related to Empire shop{% endtrans %}.</h4>
<form role="form" id="user-tickets" autocomplete="off">
{% set productQuestion = (product ? true : false) %}
<div class="form-group">
<label for="ticketName">{% trans %}Subject{% endtrans %}</label>
<input type="text" class="form-control" id="ticketName">
</div>
<div class="form-group">
<label for="ticket_content">{% trans %}Message{% endtrans %}</label>
<textarea class="form-control" id="ticket_content"></textarea>
</div>
<div class="form-group">
<label for="ticket_type">{% trans %}Choose ticket type{% endtrans %}</label>
{% set ticketTypes = get_all_possible_ticket_types() %}
<select class="form-control" id="ticket_type" {{ productQuestion ? "disabled" }}>
{% for ticketType in ticketTypes %}
<option value="{{ ticketType }}" {{ productQuestion and ticketType == "Product Question" ? "selected" }}>{{ ticketType | trans }}</option>
{% endfor %}
</select>
</div>
<div id="product-ticket-search-holder" {{ not productQuestion ? "hidden" }}>
<div class="form-group">
<label for="product-ticket-search">{% trans %}Choose product{% endtrans %}</label>
<select id="product-ticket-search" class="form-control" {{ productQuestion ? "disabled" }}>
{% if productQuestion %}
<option value="{{ product.id }}" selected >{{ product.productName | trans }}</option>
{% endif %}
</select>
</div>
</div>
</form>
<div class="form-group">
<label for="attachment">{% trans %}Upload file{% endtrans %}</label>
<form action="#" class="upload-drop-zone dropzone" id="attachment"></form>
</div>
<button type="button" id="ticket_submit" class="btn btn-primary">{% trans %}SEND{% endtrans %}</button>
</div>
user-single-ticket.html.twig
{% extends constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireUserPlugin/views/User/user-profile.html.twig' %}
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('Resource/Plugins/EmpireUserPlugin/css/user_profile_frontend_compiled.css') }}"/>
{% endblock %}
{% block mainContent %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireUserPlugin/views/User/user-single-ticket-content.html.twig' %}
{% endblock %}
{% block custom %}
<script>
var twig_username = "{{ get_logged_user().username }}";
</script>
<script src="{{ asset ('Resource/FrontendTheme/js/view_ticket_compiled.js') }}"></script>
{% endblock %}
user-single-ticket-content.html.twig
{% set user = ticket.user %}
<div class="col-m-9">
<h2>{{ ticket.name }}</h2>
<div class="wrap">
<div class="col-m-6">
<h4>{% trans %}Ticket information{% endtrans %}</h4>
<table class="table table-striped">
<tr>
<td>{% trans %}Ticket ID{% endtrans %}</td>
<td>#{{ ticket.id }}</td>
</tr>
<tr>
<td>{% trans %}Ticket type{% endtrans %}</td>
<td>{{ ticket.type|trans }}</td>
</tr>
<tr>
<td>{% trans %}Date{% endtrans %}</td>
<td>{{ ticket.createdAt|date('d.m.Y') }}</td>
</tr>
<tr>
<td>{% trans %}File{% endtrans %}</td>
<td>
{% if ticket.file %}
<a download href="{{ ticket.file.getWebPath }}" class="ticket-download">{% trans %}Download attachment{% endtrans %}</a>
{% endif %}
</td>
</tr>
{% if ticket.product %}
<tr>
<td><span>{% trans %}Product{% endtrans %}</span></td>
<td>
<a href="{{ path('product_front_slug',{'_locale': locale, 'slug': ticket.product.slug}) }}">{{ ('product.' ~ ticket.product.id ~ '.name') | trans({}, 'product-entities') }}</a>
</td>
</tr>
{% endif %}
</table>
</div>
<div class="col-m-6">
<div class="user-img-holder">
{% if user.baseImageUrl %}
<img src="{{ user.baseImageUrl }}">
{% else %}
<img src="{{ asset('Resource/FrontendTheme/images/avatar.jpg') }}" class="img-responsive" alt="">
{% endif %}
</div>
<h3>{{ user.firstName }} {{ user.lastName }}</h3>
<p> {{ user.username }} </p>
</div>
</div>
<h4>{% trans %}Comment{% endtrans %}</h4>
<ul>
{% for comment in ticket.comments %}
<li class="{{ get_logged_user().username == comment.user.username ? 'out' : 'in' }}">
<div class="user-img-holder">
{% if comment.user.baseImageUrl %}
<img alt="" src="{{ comment.user.baseImageUrl }}" class="img-responsive">
{% else %}
<img src="{{ asset('Resource/FrontendTheme/images/avatar.jpg') }}" alt="" class="img-responsive">
{% endif %}
</div>
<div class="user-ticket-content">
<a href="javascript:;"> {{ comment.user.firstName }} {{ comment.user.lastName }}</a>
<span> {{ comment.createdAt|date('d.m.Y H:i') }} </span>
<span> {{ comment.content|trans }} </span>
{% if comment.baseFileUrl %}
<a rel="gallery" href="{{ comment.baseFileUrl }}" data-fancybox-group="gallery" class="fancybox">
<span class="{{ get_logged_user().username == comment.user.username ? 'pull-right' : 'pull-left' }}"><i class="fa fa-paperclip"></i> {% trans %}View file{% endtrans %}</span>
</a>
{% endif %}
</div>
</li>
{% endfor %}
</ul>
<form role="form" id="user-tickets" autocomplete="off">
<div class="form-group">
<textarea type="text" class="form-control" placeholder="{% trans %}Enter message{% endtrans %}" id="comment_content"></textarea>
</div>
</form>
<div class="form-group">
<label for="attachment">{% trans %}Upload file{% endtrans %}</label>
<form action="#" class="upload-drop-zone dropzone" id="attachment"></form>
</div>
<div class="form-group">
<button type="button" id="comment_send" class="btn btn-primary">{% trans %}Send ticket{% endtrans %}</button>
</div>
</div>
This is an AJAX loaded template for new comments on ticket overview.
user-ticket-comment.html.twig
<li class="{{ get_logged_user().username == comment.user.username ? 'out' : 'in' }}">
<img alt="" src="{{ comment.user.baseImageUrl }}">
<div>
<a href="javascript:;"> {{ comment.user.firstName }} {{ comment.user.lastName }}</a>
<span> {{ comment.createdAt|date('d.m.Y H:i') }} </span>
<span> {{ comment.content|trans }} </span>
{% if comment.baseFileUrl %}
<a rel="gallery" href="{{ comment.baseFileUrl }}" data-fancybox-group="gallery" class="fancybox">
<span class="{{ get_logged_user().username == comment.user.username ? 'pull-right' : 'pull-left' }}"><i class="fa fa-paperclip"></i> {% trans %}View file{% endtrans %}</span>
</a>
{% endif %}
</div>
</li>
Coupons
Available coupons are also listed on the Coupons page of User profile. They are organized in tabs and distinct by usage (active and used).
user-coupons.html.twig
{% extends '@userPluginViews/User/user-coupons.html.twig' %}
{% set extendsFromTheme = true %}
{% block htmlContent %}
<div class="col-m-9 ">
<h2>{% trans %}Coupons{% endtrans %}</h2>
<ul class="nav nav-tabs" role="tablist">
<li class="active"><a href="#active" aria-controls="active" role="tab" data-toggle="tab">{% trans %}Active coupons{% endtrans %}</a></li>
<li><a href="#used" aria-controls="used" role="tab" data-toggle="tab">{% trans %}Used coupons{% endtrans %}</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="active">
{% if activeCouponsCount > 0 %}
<table class="table table-striped">
<thead>
<tr class="tickets-row">
<td>{% trans %}Coupon code{% endtrans %}</td>
<td>{% trans %}Type{% endtrans %}</td>
<td>{% trans %}Times used{% endtrans %}</td>
<td>{% trans %}Maximum time use{% endtrans %}</td>
<td>{% trans %}Valid from{% endtrans %}</td>
<td>{% trans %}Valid to{% endtrans %}</td>
</tr>
</thead>
<tbody id="user-active-coupons-table">
{% for coupon in activeCoupons if coupon.endsAt > date() %}
<tr>
<td>{{ coupon.code }}</td>
<td>
{% if coupon.type %}
{% set type = coupon.type == 'item_count' ? 'Item count'|trans : 'Item total'|trans %}
{#{{ coupon.type }}#}
{{ type }}
{% endif %}
</td>
<td>{{ coupon.promotionType == 'coupon' ? coupon.numberOfUses : '~' }}</td>
<td>{{ coupon.promotionType == 'coupon' ? coupon.usageLimit : '~' }}</td>
<td>{{ coupon.startsAt|date ('d.m.Y')}}</td>
<td>{{ coupon.endsAt|date ('d.m.Y')}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>{% trans %}No active coupons{% endtrans %}</p>
{% endif %}
{% if activeCouponsCount > 5 %}
<ul class="uk-pagination">
<li><a href="" id="active-pagination-first-page"><i class="fa fa-angle-double-left" ></i></a></li>
<li><a href="" id="active-pagination-previous-page"><i class="fa fa-angle-left" ></i></a></li>
<li>
<ul class="uk-pagination" id="active-post-page-pagination">
</ul>
</li>
<li><a href="" id="active-pagination-next-page"><i class="fa fa-angle-right"></i></a></li>
<li><a href="" id="active-pagination-last-page"><i class="fa fa-angle-double-right"></i></a></li>
</ul>
{% endif %}
</div>
<div role="tabpanel" class="tab-pane" id="used">
{% if usedCouponsCount > 0 %}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr class="tickets-row">
<td>{% trans %}Coupon code{% endtrans %}</td>
<td>{% trans %}Type{% endtrans %}</td>
<td>{% trans %}Times used{% endtrans %}</td>
<td>{% trans %}Maximum time use{% endtrans %}</td>
</tr>
</thead>
<tbody id="user-used-coupons-table">
{% for coupon in usedCoupons %}
<tr>
<td>{{ coupon.code }}</td>
<td>
{% if coupon.type %}
{% set type = coupon.type == 'item_count' ? 'Item count'|trans : 'Item total'|trans %}
{#{{ coupon.type }}#}
{{ type }}
{% endif %}
</td>
<td>{{ coupon.numberOfUses }}</td>
<td>{{ coupon.usageLimit }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>{% trans %}No used coupons{% endtrans %}</p>
{% endif %}
</div>
{% if usedCouponsCount > 5 %}
<ul class="uk-pagination">
<li><a href="" id="used-pagination-first-page"><i class="fa fa-angle-double-left" ></i></a></li>
<li><a href="" id="used-pagination-previous-page"><i class="fa fa-angle-left" ></i></a></li>
<li>
<ul class="uk-pagination" id="used-post-page-pagination">
</ul>
</li>
<li><a href="" id="used-pagination-next-page"><i class="fa fa-angle-right"></i></a></li>
<li><a href="" id="used-pagination-last-page"><i class="fa fa-angle-double-right"></i></a></li>
</ul>
{% endif %}
</div>
</div>
</div>
{% endblock htmlContent %}
user-coupons-pagination.html.twig
{% for coupon in coupons %}
{% if coupon.used %}
<tr>
<td>{{ coupon.code }}</td>
<td>
{% if coupon.type %}
{% set type = coupon.type == 'item_count' ? 'Item count'|trans : 'Item total'|trans %}
{{ type }}
{% endif %}
</td>
<td>{{ coupon.numberOfUses }}</td>
<td>{{ coupon.usageLimit }}</td>
</tr>
{% else %}
<tr>
<td>{{ coupon.code }}</td>
<td>
{% if coupon.type %}
{% set type = coupon.type == 'item_count' ? 'Item count'|trans : 'Item total'|trans %}
{{ type }}
{% endif %}
</td>
<td>{{ coupon.numberOfUses }}</td>
<td>{{ coupon.usageLimit }}</td>
<td>{{ coupon.startsAt|date ('d.m.Y')}}</td>
<td>{{ coupon.endsAt|date ('d.m.Y') }}</td>
</tr>
{% endif %}
{% endfor %}
Wishlist
The last item on the User profile is Wishlist. In this template, wishlist items are displayed with the options to view them, add to cart and remove. If the Wishlist Plugin is included, a layout is set
user-wishlist.html.twig
{% extends constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireUserPlugin/views/User/user-profile.html.twig' %}
{% set user = get_logged_user() %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('Resource/Plugins/EmpireUserPlugin/css/user_wishlist_frontend_compiled.css') }}"/>
{% endblock %}
{% block mainContent %}
<div class="col-m-9">
<h2>{% trans %}Wishlists{% endtrans %}</h2>
<div class="panel-group clearfix" id="wishlist-accordion" role="tablist" aria-multiselectable="true">
<div class="panel panel-default accordion-item">
{% set userWishlist = user.getWishList %}
{% if user and userWishlist|length %}
{% set erp_prices = erp_get_calculated_prices(userWishlist) %}
{% endif %}
<div class="panel-heading" role="tab" id="headingOne">
<h4 class="panel-title">
<a role="button" data-toggle="collapse" data-parent="#wishlist-accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne" class="collapsed">
<i class="fa fa-chevron-right more-less"></i>
{% trans %}General wishlist{% endtrans %} <span id="badge-0" class="badge pull-right">{{ userWishlist|length }}</span>
</a>
</h4>
</div>
<div id="general-user-wishlist-holder-dnt">
<div id="collapseOne" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingOne">
{% if userWishlist|length %}
<div class="panel-body wishlist-table table-responsive">
<table class="table table-striped">
<thead>
<tr>
<td>{% trans %}Image{% endtrans %}</td>
<td>{% trans %}Product name{% endtrans %}</td>
<td>{% trans %}SKU{% endtrans %}</td>
<td>{% trans %}Price{% endtrans %}</td>
<td>{% trans %}Availability{% endtrans %}</td>
<td>{% trans %}Action{% endtrans %}</td>
</tr>
</thead>
<tbody>
{% for item in userWishlist %}
{% set finalPrice = get_price(item) %}
<tr data-multiple-wl-item="{{ item.id }}">
<td>
<a href="{{ path('product_front_slug',{'_locale': app.request.locale, 'slug': item.slug}) }}">
{% if item.baseImageUrl %}
<img src="{{ getDefaultExtension(item.baseImageUrl) }}" alt="{{ item.getProductName }}" class="img-responsive">
{% else %}
<img src="{{ asset('Resource/FrontendTheme/images/default_product.png') }}" alt="{{ item.getProductName }}" class="img-responsive">
{% endif %}
</a>
</td>
<td>
<a href="{{path('product_front_slug',{'_locale': app.request.locale, 'slug': item.slug})}}">{{ ('product.' ~ item.id ~ '.name') | trans({}, 'product-entities') }} </a>
</td>
<td>
<p>{{ item.sku }}</p>
</td>
<td>
{% if not item.onAction %}
<span>{{ finalPrice | alligator_json_convert() | alligator_money_format }}</span>
{% else %}
<div>{{ item.priceOnAction | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</div>
<div style="text-decoration: line-through; font-size:11px; margin-right: 5px; color: #999;">
{{ finalPrice | alligator_json_convert() | alligator_money_format }}
</div>
{% endif %}
</td>
<td>
{% if item.simpleProduct.numberInStock > 0 %}
<p class="in-availability"> <i class="fa fa-circle"></i> {% trans %}In stock{% endtrans %}</p>
{% else %}
<p class="not-availability"> <i class="fa fa-circle"></i> {% trans %}Not Available{% endtrans %}</p>
{% endif %}
</td>
<td class="wishlist-btn-holder">
<a href="{{ path('product_front_slug',{'_locale': app.request.locale, 'slug': item.slug}) }}">
<i class="fa fa-eye"></i>
</a>
<a class="removewish removeFromGeneralWishlist general" data-product='{{ item.id }}'>
<i class="fa fa-trash"></i>
</a>
<a class="add-to-cart-general" href="#">
{% trans %}Shop now{% endtrans %}
</a>
</td>
</tr>
<script>
{% set numberInStock = (item.simpleProduct is defined ? item.simpleProduct.numberInStock : item.numberInStock ) %}
var productObj = {
id: {{ item.id }},
slug: "{{ item.slug }}",
product_name: "{{ item.productName }}",
{% if alligator_can_see_product_price(item.permissions) %}
current_price : {{ finalPrice }},
{% endif %}
simple_product: {
{% if numberInStock != NULL %}
number_in_stock: {{ numberInStock }}
{% endif %}
}
};
var index = {{ loop.index0 }};
var customBtn = new AddToCartButton( $('.wishlist-cart .add-to-cart-general')[index], productObj);
{{ item.permissions is defined and alligator_check_product_add_to_cart_permission(item.permissions) == false ? 'customBtn.setNotAllowed();' }}
var removeBtn = new RemoveWishListButton($('.wishlist-btn-holder .general')[index], {{ item.id }});
</script>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="panel-body">
<ul>
<li>{% trans %}No items in wishlist{% endtrans %}</li>
</ul>
</div>
{% endif %}
</div>
</div>
</div>
{% block userWishlistPlugins %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'multiple-wishlists-listing'} %}
{% endblock userWishlistPlugins %}
</div>
</div>
</div>
{% endblock %}
{% block custom %}
<script>
if (typeof wishListItems === 'undefined') {
var wishListItems = [];
}
{% for item in user.getWishList %}
wishListItems.push({{ item.id }});
{% endfor %}
var parentPrototypeRemoveWishlist = RemoveWishListButton.prototype.successFunction;
RemoveWishListButton.prototype.successFunction = function (ctx, data) {
parentPrototypeRemoveWishlist(ctx, data);
if (parseInt(data.status) == 200) {
var badge = $('#badge-0');
var generalBadgeRemainItems = parseInt(badge.html())-1;
badge.html(generalBadgeRemainItems);
if (generalBadgeRemainItems === 0) {
$('#general-user-wishlist-holder-dnt').html('<div class="panel-body wishlist-table"><ul><li class="no-wishlist-data">' + Translator.trans('No items in wishlist') + '</li></ul></div>');
}
}
};
function toggleIcon(e) {
$(e.target)
.prev('.panel-heading')
.find(".more-less")
.toggleClass('fa-chevron-right fa-chevron-down');
}
</script>
{% endblock %}
Compare
Product Compare plugin is one of core plugins of F-Webshop. Main template for prouduct comparison is compare-page-content.html.twig. Its child template product-compare-listing.html.twig lists all of the products added to compare. Information about the products is available, along with actions of add to cart and remove from list.
compare-page-content.html.twig
{% extends constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/master.html.twig' %}
{% block stylesheets %}
<link rel="stylesheet" href="{{ asset('Resource/Plugins/EmpireProductComparePlugin/product-compare_style.css') }}"/>
{% endblock %}
{% set userSettings = get_current_user_settings() %}
{% if get_logged_user() and listOfAllProducts|length %}
{% set erp_prices = erp_get_calculated_prices(listOfAllProducts) %}
{% endif %}
{% block content %}
{% include '@userPluginViews/Layout/modal-exception.html.twig' %}
<div class="wrapper">
<h1>{% trans %}Product compare lists{% endtrans %}</h1>
<div class="panel-group" id="compare-list-accordion" role="tablist" aria-multiselectable="true">
{% if compareLists %}
{% for compareList in compareLists %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireProductComparePlugin/views/product-compare-listing.html.twig' %}
{% endfor %}
{% else %}
<h4>{% trans %}No products for comparison{% endtrans %}</h4>
{% endif %}
</div>
</div>
{% block compareScripts %}
{% block custom %}
<script>
var showPriceToUser = [];
</script>
{% include '@productComparePluginViews/product-compare-listing-js.html.twig' %}
{% endblock %}
{% endblock compareScripts %}
{% endblock %}
product-compare-listing.html.twig
{% set products = compareList.products %}
{% block compareListingContent %}
<div class="panel panel-default accordion-item">
<button class="removeMultipleCompareList btn btn-danger" data-id="{{ compareList.id }}">{% trans %}Delete list{% endtrans %}</button>
<div class="panel-heading" role="tab" id="heading{{ compareList.id }}">
<a role="button" data-toggle="collapse" data-parent="#compare-list-accordion" href="#collapse{{ compareList.id }}" aria-expanded="true" aria-controls="collapse{{ compareList.id }}" class="collapsed">
<h4 class="panel-title">
<i class="fa fa-chevron-right"></i> {{ compareList.name }}
</h4>
</a>
</div>
<div id="collapse{{ compareList.id }}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading{{ compareList.id }}">
<span id="badge-{{ compareList.id }}" hidden>{{ products|length }}</span>
<div class="panel-body">
{% if products|length %}
<div class="table-responsive">
<table class="table table-striped">
<tbody>
<tr>
<td>{% trans %}Image{% endtrans %}</td>
{% for product in products %}
<td class="column-{{ compareList.id~'-'~product.id }}">
<a href="{{path('product_front_slug',{'_locale': locale, 'slug': product.slug})}}">
{% if product.baseImageUrl %}
<img src="{{ getDefaultExtension(product.baseImageUrl) }}" alt="Compare Image" class="img-responsive">
{% else %}
<img src="{{ asset('Resource/FrontendTheme/images/default_product.png') }}" alt="Compare Image" class="img-responsive">
{% endif %}
</a>
</td>
{% endfor %}
</tr>
<tr>
<td>{% trans %}Product Name{% endtrans %}</td>
{% for product in products %}
<td class="column-{{ compareList.id~'-'~product.id }}">
<a href="{{path('product_front_slug',{'_locale': locale, 'slug': product.slug})}}">{{ ('product.' ~ product.id ~ '.name') | trans({}, 'product-entities') }}</a>
</td>
{% endfor %}
</tr>
<tr>
<td>{% trans %}SKU{% endtrans %}</td>
{% for product in products %}
<td class="column-{{ compareList.id~'-'~product.id }}">
<a>{{ product.sku }}</a>
</td>
{% endfor %}
</tr>
<tr>
<td>{% trans %}Category{% endtrans %}</td>
{% for product in products %}
<td class="column-{{ compareList.id~'-'~product.id }}">
{% for category in product.categories %}
<span>{{ category.getCategoryName }}</span> <br>
{% endfor %}
</td>
{% endfor %}
</tr>
<tr>
<td >{% trans %}Price{% endtrans %}</td>
{% for product in products %}
<td class="column-{{ compareList.id~'-'~product.id }}">
{% if alligator_can_see_product_price(product.permissions) %}
{% set finalPrice = get_price(product) %}
<div id="product-price-{{ product.id ~ '-' ~ compareList.id }}">
{% if not product.onAction %}
<span>{{ finalPrice | alligator_json_convert() | alligator_money_format }}</span>
{% else %}
<span>{{ product.priceOnAction | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</span>
<div style="text-decoration: line-through; font-size:12px; margin-right: 5px; color: #999;">
{{ finalPrice | alligator_json_convert() | alligator_money_format }}
</div>
{% endif %}
</div>
{% else %}
<span>/</span>
{% endif %}
</td>
{% endfor %}
</tr>
<tr>
<td> </td>
{% for product in products %}
<td class="compare-add column-{{ compareList.id~'-'~product.id }}">
<a class="add-to-cart-{{ compareList.id }}" href="#">
{% trans %}Shop now{% endtrans %}
</a>
</td>
{% endfor %}
</tr>
<tr>
<td>{% trans %}Availability{% endtrans %}</td>
{% for product in products %}
<td class="availability column-{{ compareList.id~'-'~product.id }}">
{% if product.simpleProduct.numberInStock > 0 %}
<p class="in-availability"> <i class="fa fa-circle"></i> {% trans %}In stock{% endtrans %}</p>
{% else %}
<p class="not-availability"> <i class="fa fa-circle"></i> {% trans %}Not Available{% endtrans %}</p>
{% endif %}
</td>
{% endfor %}
</tr>
<tr>
<td >{% trans %}Actions{% endtrans %}</td>
{% for product in products %}
<td class="action-compare column-{{ compareList.id~'-'~product.id }}">
<a class="action-compare-view" href="{{path('product_front_slug',{'_locale': app.request.locale, 'slug': product.slug})}}">
<i class="fa fa-eye"></i>
</a>
<a class="removeFromNamedCompareList action-compare-delete" data-product-id='{{ product.id }}' data-compare-list-id="{{ compareList.id }}">
<i class="fa fa-trash"></i>
</a>
</td>
{% endfor %}
</tr>
</tbody>
</table>
</div>
<script>
{% for product in products %}
{% set numberInStock = (product.simpleProduct is defined ? product.simpleProduct.numberInStock : product.numberInStock ) %}
var productObj = {
id: {{ product.id }},
slug: "{{ product.slug }}",
product_name: "{{ product.productName }}",
{% if alligator_can_see_product_price(product.permissions) %}
current_price : {{ get_price(product) }},
{% endif %}
simple_product: {
{% if numberInStock != NULL %}
number_in_stock: {{ numberInStock }}
{% endif %}
},
quantity_step: {{ product.quantityStep ? product.quantityStep : 1 }}
};
var index = {{ loop.index0 }};
var customBtn = new AddToCartButton( $('.compare-add .add-to-cart-{{ compareList.id }}')[index], productObj);
{{ product.permissions is defined and alligator_check_product_add_to_cart_permission(product.permissions) == false ? 'customBtn.setNotAllowed();' }}
{% endfor %}
</script>
{% else %}
<h4>{% trans %}No products for comparison{% endtrans %}</h4>
{% endif %}
<div>
</div>
</div>
</div>
</div>
{% endblock %}
Checkout
Checkout plugin handles the payment process and order completion. First we have a template for quick cart overview that is accessible from every page. It lists all items that are added to cart.
quick-cart.html.twig
{% block quickCartContent %}
{% if order is not null %}
{% set locale = app.request.get('_locale') %}
{% set orderNumber = order.items | length %}
<div class="minicart-items-wrapper">
<ol class="minicart-items qc-product-wrap">
{% for item in order.items if item.product != null %}
<li class="clearfix">
<a href="#" title="The Name Product">
{% if item.productAttValues|length and item.productAttValues[0].baseImageUrl %}
<img src=" {{ item.productAttValues[0].baseImageUrl }}" class="img img-responsive" alt="">
{% elseif item.product.baseImageUrl %}
<img src=" {{ getDefaultExtension(item.product.baseImageUrl) }}" class="img img-responsive" alt="">
{% else %}
<img src="{{ asset('Resource/FrontendTheme/images/default_product.png') }}" alt="">
{% endif %}
</a>
<div>
<a href="{{ path('product_front_slug',{'_locale': locale, 'slug': item.product.slug}) }}">{{ ('product.' ~ item.product.id ~ '.name') | trans({}, 'product-entities') }}</a>
<div>
<span>{{ item.quantity }}</span>
<p>x</p>
<span> {{ item.unitPrice | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</span>
</div>
<span> {{ (item.quantity * item.unitPrice) | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</span>
<a class="action delete remove_item_{{ item.id }}" href="#" id="remove_item_{{ item.id }}">
<i class="fa fa-close"></i>
</a>
</div>
</li>
<script>
var item = {
unit_price: {{ item.unitPrice ? item.unitPrice : '0' }},
product: {
id: {{ item.product.id }},
simple_product: {
number_in_stock: {{ item.product.simpleProduct.numberInStock }}
},
quantity_step: {{ item.product.quantityStep ? item.product.quantityStep : 1 }}
},
quantity: {{ item.quantity }},
product_att_values: [
{% for value in item.productAttValues %}
{
id: {{ value.id }},
cost: {{ get_price(value) }},
sku: '{{ value.sku }}',
quantity: {{ value.quantity }},
min_quantity: {{ value.minQuantity }},
quantity_step: {{ value.quantityStep }},
display_to_user: {{ value.displayToUser ? "true" : "false" }},
price_on_action: {{ get_action_price(value) }},
attribute_values: [
{% for attr in value.attributeValues %}
{
id: {{ attr.id }}
}
{% if not loop.last %},{% endif %}
{% endfor %}
]
}
{% if not loop.last %},{% endif %}
{% endfor %}
]
};
$('.remove_item_'+{{ item.id }}).each(function(index, element) {
var removeBtn = new RemoveButton($(element)[0], item);
});
</script>
{% else %}
<p>{% trans %}No products added{% endtrans %}</p>
{% endfor %}
</ol>
</div>
{% if order.items | length %}
<div class="quick-cart-bottom">
<a href="#removeAllProductsFromCartModal" data-toggle="modal"><p>{% trans %}Remove all items{% endtrans %}</p></a>
<div>
<span>{% trans %}Total price{% endtrans %}</span>
<span>{{ order.total | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</span>
</div>
<div class="actions">
<a href="{{ path('checkout_summary', {'_locale':locale}) }}" class="btn btn-primary" type="button">{% trans %}Check out{% endtrans %}</a>
</div>
</div>
{% endif %}
{% endif %}
{% endblock quickCartContent %}
Multi-step checkout
Multi-step checkout means a checkout process with multiple steps that are enabled in the Dashboard (database).
Summary step
The first step is summary step, and the main parent template is summary.html.twig which sets the stage for child templates and includes main scripts.
summary.html.twig
{% extends '@checkoutPluginViews/summary.html.twig' %}
{% set user = get_logged_user() %}
{% if user %}
{% set coupons = user.getCoupons %}
{% endif %}
{% block content %}
{% if order and order.items | length %}
{% include '@checkoutPluginViews/coupon-modal.html.twig' %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/summary-content.html.twig' %}
{% include '@checkoutPluginViews/checkout-summary-scripts.html.twig' %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'checkout-pages-script', 'step': 'summary'} %}
{% else %}
<div class="wrapper">
<span class="alert alert-danger cart-empty-holder">{% trans %}Your cart is empty! Please add products to your cart first{% endtrans %}</span>
</div>
{% endif %}
{% endblock %}
{% block scripts %}
{% endblock %}
The direct child template is summary-content.html.twig which indludes the main child template - summary-main-content-template.html.twig.
summary-content.html.twig
{% extends '@checkoutPluginViews/summary-content.html.twig' %}
{% block summaryContent %}
<section class="container" id="checkout-summary-table-data">
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/summary-main-content-template.html.twig'%}
</section>
<script>
var couponAssigned = false;
{% if app.session.get('COUPON_PK') %}
couponAssigned = true;
{% endif %}
</script>
{% endblock summaryContent %}
Summary main content lists all products that are added to cart with images, names, prices, quanitities and the total sum. Also a button for removal is available. Beneath the table, a coupon code input is available and the option for quick adding to cart by searching products via AJAX.
Function that is used:
- getNextStep(): gets the next checkout step from the database
summary-main-content.html.twig
{% if route is not defined %}
{% set route = app.request.get('_route') %}
{% endif %}
{% if couponExists is not defined %}
{% set couponExists = false %}
{% endif %}
{% block summaryMainContent %}
{% block summaryCouponModal %}
{% include '@checkoutPluginViews/modal-coupon-check.html.twig' %}
{% endblock summaryCouponModal %}
{% block summaryCheckoutNavbar %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/checkout-navbar.html.twig' %}
{% endblock summaryCheckoutNavbar %}
<div class="form-cart">
<div class="table-cart-wrapper table-responsive">
{% set internetDiscount = 0 %}
<table class="table table-striped" id="summary-table">
<thead>
<tr>
<th class="col-sm-1">{% trans %}Image{% endtrans %}</th>
<th class="col-sm-3">{% trans %}Name{% endtrans %}</th>
<th class="col-sm-2">{% trans %}Price{% endtrans %}</th>
<th class="col-sm-3">{% trans %}Quantity{% endtrans %}</th>
<th class="col-sm-1">{% trans %}Subtotal{% endtrans %}</th>
<th class="col-sm-1"> </th>
</tr>
</thead>
{% set internetDiscount = 0 %}
{% set internetDiscountPersent = 0 %}
<tbody>
{% for orderItem in order.items %}
{% set productIdentification = orderItem.product.getId ~ '[' %}
{% set adjustments = orderItem.getAdjustmentsTotalForEachType %}
{% set discountPersent = attribute(adjustments, 'CHECKOUT_INTERNET_DISCOUNT_CACHE') is defined ? attribute(adjustments, 'CHECKOUT_INTERNET_DISCOUNT_CACHE') : 0 %}
{% if discountPersent | abs > 0 %}
{% set internetDiscount = internetDiscount + orderItem.quantity*orderItem.getUnitPrice*(- discountPersent/100) %}
{% set internetDiscountPersent = discountPersent %}
{% endif %}
{% set quantityStep = orderItem.product.quantityStep ? orderItem.product.quantityStep : 1 %}
<tr>
<td>
<a href="{{ path('product_front_slug', {'_locale': app.request.locale, 'slug': orderItem.product.slug}) }}">
{% if orderItem.productAttValues|length and orderItem.productAttValues[0].baseImageUrl %}
<img src="{{ getDefaultExtension(orderItem.productAttValues[0].baseImageUrl) }}" class="img img-responsive" alt="">
{% elseif orderItem.product.baseImageUrl %}
<img src="{{ getDefaultExtension(orderItem.product.baseImageUrl) }}" class="img img-responsive" alt=""/>
{% else %}
<img src="{{ asset('Resource/FrontendTheme/images/default_product.png') }}" alt="">
{% endif %}
</a>
{% if orderItem.product.groupProduct %}
<a href="#" class="open-bundle-modal-dnt" data-productname="{{ orderItem.product.productName }}" data-groupid="{{ orderItem.product.groupProduct.id }}">
{% trans %}See bundle product{% endtrans %}
</a>
{% endif %}
</td>
<td>
<a href="{{ path('product_front_slug', {'_locale': app.request.locale, 'slug': orderItem.product.slug}) }}">
{{ ('product.' ~ orderItem.product.id ~ '.name') | trans({}, 'product-entities') }}
</a>
</td>
<td>
{{ get_price(orderItem) | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}
</td>
<td>
<div class="number-spinner">
<button class="btn btn-primary" data-dir="dwn" data-step="{{ quantityStep }}">
<i class="fa fa-minus"></i>
</button>
<input type="text" class="form-control quantity-input" min="1" value="{{ orderItem.quantity }}" data-productidentification="{{ productIdentification }}">
<button class="btn btn-primary" data-dir="up" data-step="{{ quantityStep }}">
<i class="fa fa-plus"></i>
</button>
</div>
</td>
<td>
<span>{{ orderItem.total | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</span>
</td>
<td class="delete-btn">
<i class="fa fa-times"></i>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div id="coupon-code-container">
<div class="wrap">
<div class="col-m-6">
{% set nextRoute = getNextStep(route) %}
{% block summaryCouponCheck %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'cart-actions-coupon-add'} %}
{% block summaryStepAfterTableLayout %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'summary-step-after-table'} %}
{% endblock summaryStepAfterTableLayout %}
{% block summaryStepTableDiscounts %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'summary-step-table-discounts'} %}
{% endblock summaryStepTableDiscounts %}
{% endblock summaryCouponCheck %}
</div>
<div class="col-m-6">
<table class="table table-bordered">
<tr>
<td>{% trans %}Quantity{% endtrans %}</td>
<td id="order-item-number">{{ order.items|length }}</td>
</tr>
{% if order.getTaxTotal and order.getTaxTotal > 0 %}
<tr>
<td>{% trans %}Tax{% endtrans %}</td>
<td id="order-discount">{{ order.getTaxTotal | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</td>
</tr>
{% endif %}
{% if internetDiscount | abs > 0 %}
<tr>
<td>{% trans %}Internet discount {% endtrans %}: {{ internetDiscountPersent * (-1)| round(2, 'ceil')| number_format(2, '.') }} %</td>
<td id="internet-discount"> {{ internetDiscount | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }} </td>
</tr>
{% endif %}
<tr id="grand-total">
<td>{% trans %}Grand total{% endtrans %}</td>
<td id="order-grand-total">{{ order.getTotal| alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</td>
</tr>
</table>
</div>
</div>
</div>
<div class="checkout-actions">
<a class="button-reset btn btn-danger" id="btn-back-to-shop" href="{{ path('homepage') }}"> {% trans %}Back to top{% endtrans %}</a>
<a class="button-reset btn btn-primary btn-proceed" href="{{ path(nextRoute) }}">{% trans %}Proceed{% endtrans %}</a>
</div>
<div class="modal fade" id="bundleProductModal" tabindex="-1" role="dialog" aria-labelledby="modalExceptionLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true"><i class="fa fa-close"></i></span>
</button>
<h4 id="bundleProductModalTitle" class="modal-title"></h4>
</div>
<div class="modal-body">
<div id="bundleProductModalBody"></div>
<div class="modal-footer">
<button tabindex="2" id="bundle-modal-cancel" data-dismiss="modal" aria-label="Close" >{% trans %}CLOSE{% endtrans %}</button>
</div>
</div>
</div>
</div>
</div>
{% endblock summaryMainContent %}
Above all content on these checkout pages, we have a checkout navbar that serves as a navigator through checkout steps.
checkout-navbar.html.twig
{% block checkoutNavbarContent %}
{% if route is not defined %}
{% set route = app.request.get('_route') %}
{% endif %}
<ul id="checkout-steps">
{% for step in getCurrentSteps(route)%}
{% if step.route!='' %}
<li class="{{ step.status }}"><a href="{{ path(step.route) }}"><span>{{ loop.index }}</span> {{ step.name|trans }}</a></li>
{% else %}
<li class="{{ step.status }}"> <a href="#"><span>{{ loop.index }}</span> {{ step.name|trans }}</a></li>
{% endif %}
{% endfor %}
</ul>
{% endblock %}
cart-actions-coupon-add.html.twig
{% if get_logged_user() or get_current_user_settings().allowCouponIfUserNotLogged %}
<tr>
<td>
<div class="coupon-holder">
{% if couponExists %}
<input id="coupon-code" type="text" {% if couponExists %} disabled placeholder="{% trans %}Coupon already assigned{% endtrans %}" {% else %}{% endif %}/>
{% else %}
<input id="coupon-code" placeholder="Code for discount" type="text" class="form-control">
{% endif %}
<button class="btn btn-success" id="btn-coupon">
<i class="fa fa-check"></i>
</button>
<div id="couponActivationButton" {% if not couponExists %} hidden {% endif %}>
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/apply-coupon.html.twig' %}
</div>
</div>
</td>
</tr>
{% endif %}
Address step
This step sets users order address. Parent template include main content template, scripts and necessary modals.
Functions that are used:
- alligator_session_order(): gets session order
- getCurrentPickUpInTheStore(): gets Pick up in the store settings from the database
- getCurrentFindMyLocation(): gets Find my location settings from the database
address.html.twig
{% extends constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/master.html.twig' %}
{% block stylesheets %}
<link href="{{ asset('Resource/FrontendTheme/css/checkout_summary_compiled.css') }}" rel="stylesheet">
{% endblock %}
{% set user = get_logged_user() %}
{% set order = alligator_session_order() %}
{% set address = null %}
{% set orderShippingAddressSelected = 0 %}
{% if user and not order.address %}
{% set address = user.getAddress %}
{% elseif user and order.address %}
{% set address = order.address %}
{% set orderShippingAddressSelected = order.address.id %}
{% if order.address.id == user.getAddress.id %}
{% set orderShippingAddressSelected = 0 %}
{% else %}
{% for addr in user.getShippingAddresses %}
{% if addr.id == order.address.id or order.address.id == user.getAddress.id %}
{% set orderShippingAddressSelected = 0 %}
{% endif %}
{% endfor %}
{% endif %}
{% endif %}
{% block content %}
{% if user %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireUserPlugin/views/Layout/addShippingAddressModal.html.twig' %}
{% endif %}
<section class="wrapper">
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/address-content.html.twig' %}
</section>
{% endblock %}
{% block scripts %}
<script>
var orderShippingAddressSelected = 0;
</script>
{% if user %}
<script>
var userLoggedIn = true;
var userId = {{ user.id }};
{% if orderShippingAddressSelected %}
orderShippingAddressSelected = {{ orderShippingAddressSelected }};
{% endif %}
var pickUpInTheStoreEnabled = false;
</script>
{% endif %}
<script src="{{ asset ('Resource/FrontendTheme/js/address_step_checkout_compiled.js') }}"></script>
{% if getCurrentPickUpInTheStore() or getCurrentFindMyLocation() %}
<script>
pickUpInTheStoreEnabled = true;
var userPinIcon = '{{ asset('Resource/Plugins/EmpireStoreLocatorPlugin/img/user-pin-256.png') }}';
</script>
<script src="{{ asset('Resource/Plugins/EmpireCheckoutPlugin/js/pick_up_in_the_store_scripts_compiled.js') }}"></script>
{% endif %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'checkout-pages-script', 'step': 'address'} %}
{% endblock %}
This is the main content address template. It lists available addresses first, and offers option to select the desired address or add a new one. Also there is an option to detect location and choose picking up in the store instead of the delivery.
address-content.html.twig
{% extends '@checkoutPluginViews/address-content.html.twig' %}
{% block addressContent %}
{% block addressCheckoutNavbar %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/checkout-navbar.html.twig' %}
{% endblock addressCheckoutNavbar %}
{% block addressCustomer %}
{% set nextRoute = getNextStep(app.request.get('_route')) %}
{% if address %}
<table class="table table-bordered address-summary-table" id="summary-table">
<tbody>
<tr>
<td>{% trans %}Billing Address{% endtrans %}</td>
<td id="billing_address">{% if order.getAddress %}{{ order.getAddress.toString }} {% endif %}</td>
</tr>
{% if user %}
<tr>
<td>{% trans %}Shipping address{% endtrans %}</td>
<td>
<table class="form-group" >
<tr>
<td >{% trans %}Choose your shipping address{% endtrans %}:</td>
<td id="shipping-addresses-container-dnt">
{% for address in user.getShippingAddresses %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireUserPlugin/views/User/address-template-component.html.twig' %}
{% endfor %}
</td>
</tr>
</table>
<table id="add-shipping-address-holder">
<tr>
<td>
<button class="btn btn-success" id="add-new-shipping-address" data-toggle="modal" data-target="#shippingAddressModal">{% trans %}Add new shipping address{% endtrans %}</button>
</td>
</tr>
</table>
</td>
</tr>
{% endif %}
</tbody>
</table>
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/apply-coupon.html.twig' %}
{% else %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/new-address.html.twig' %}
{% endif %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/find-my-location-button.html.twig' %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/pick-up-in-the-store-content.html.twig' %}
<div class="checkout-actions">
<a class="btn btn-danger" id="btn-back-to-shop" href="{{ path('homepage') }}">{% trans %}Back to shop{% endtrans %}</a>
<a class="btn-proceed btn btn-primary" id='address-to-shipping' href="{{ path(nextRoute) }}">{% trans %}Proceed{% endtrans %}</a>
</div>
{% endblock addressCustomer %}
{% endblock addressContent %}
new-address.html.twig
{% block newAddressContent %}
<form role="form" id="address-form">
{% set anonUser = order.anonymous ? order.anonymous | split(' ') : null %}
<div class="form-group">
<label for="first_name">{% trans %}First name{% endtrans %}</label>
<input type="text" name="first_name" class="form-control" id="first_name" value="{{ anonUser | length ? anonUser[0] : '' }}">
</div>
<div class="form-group">
<label for="last_name">{% trans %}Last name{% endtrans %}</label>
<input type="text" name="last_name" class="form-control" id="last_name" value="{{ anonUser | length ? anonUser[1] : '' }}">
</div>
<div class="form-group">
<label>{% trans %}Email address{% endtrans %}</label>
<input type="text" class="form-control" name="email" id="email" value="{{ order.anonymousMail ? order.anonymousMail : null }}">
</div>
<div class="form-group">
<label>{% trans %}Please enter your billing address below{% endtrans %}</label>
<input type="text" class="form-control" name="address" id="autocomplete" value="{{ order.getAddressToString }}" onfocus="geolocate()" autocomplete="off" placeholder="{% trans %}Enter a location{% endtrans %}">
</div>
{% if order.address %}
{% set orderAddress = order.address %}
<div class="form-group">
<label for="street_number">{% trans %}Street number{% endtrans %}</label>
<input type="text" name="street_number_order" class="form-control" id="street_number" value="{{ orderAddress.streetNumber }}">
</div>
<div class="form-group">
<label for="route">{% trans %}Street {% endtrans %}</label>
<input type="text" name="street" class="form-control" id="route" value="{{ orderAddress.street }}">
</div>
<div class="form-group">
<label for="country">{% trans %}Country{% endtrans %}</label>
<input type="text" id="country" class="form-control" name="country" required value="{{ orderAddress.country }}">
</div>
<div class="form-group">
<label for="locality">{% trans %}City{% endtrans %}</label>
<input type="text" name="city" class="form-control" id="locality" value="{{ orderAddress.city }}">
</div>
<div class="form-group">
<label for="postal_code">{% trans %}ZIP{% endtrans %}</label>
<input type="text" name="postcode_order" class="form-control" id="postal_code" value="{{ orderAddress.postcode }}">
</div>
{% else %}
<div class="form-group">
<label for="route">{% trans %}Street {% endtrans %}</label>
<input type="text" name="street" class="form-control" id="route" disabled="true">
</div>
<div class="form-group">
<label for="street_number">{% trans %}Street number{% endtrans %}</label>
<input type="text" name="street_number_order" class="form-control" id="street_number" disabled="true">
</div>
<div class="form-group">
<label for="country">{% trans %}Country{% endtrans %}</label>
<input type="text" id="country" class="form-control" name="country" required disabled="true">
</div>
<div class="form-group">
<label for="locality">{% trans %}City{% endtrans %}</label>
<input type="text" name="city" class="form-control" id="locality" disabled="true">
</div>
<div class="form-group">
<label for="postal_code">{% trans %}ZIP{% endtrans %}</label>
<input type="text" name="postcode_order" class="form-control" id="postal_code" disabled="true">
</div>
<div class="form-group">
<label for="phone_number">{% trans %}Phone{% endtrans %}</label>
<input type="text" name="phone_number" class="form-control" id="phone_number" disabled="true">
</div>
{% endif %}
<div class="form-group">
<label>{% trans %}Please enter email for this address{% endtrans %}</label>
<input type="text" class="form-control" name="addressEmail" id="addressEmail">
</div>
<div class="form-group">
<a id='address-save' href="#">{% trans %}SAVE ADDRESS{% endtrans %}</a>
</div>
<p>{% trans %}Your billing address has been saved.{% endtrans %}</p>
{% set nextRoute = getNextStep(app.request.get('_route')) %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/apply-coupon.html.twig' %}
</form>
{% endblock %}
{% block newAddressScripts %}
<script>
var userPreviouslySelectedAddress = false;
{% if order.address %}
userPreviouslySelectedAddress = true;
{% endif %}
</script>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=AIzaSyCPURcNo9iGaHTvam7DO4lrN7hLkTutaPA&libraries=places"></script>
<script src="{{ asset('Resource/Plugins/EmpireCheckoutPlugin/js/new_address_scripts_compiled.js') }}"></script>
{% endblock %}
find-my-location-button.html.twig
{% block findMyLocation %}
{% if getCurrentFindMyLocation() %}
<div id="find-my-location-options-table">
<label for="findMyLocation">
<input type="checkbox" name="findMyLocation" id="findMyLocation"/>
{% trans %}Find my location{% endtrans %}
</label>
</div>
{% endif %}
{% endblock findMyLocation %}
pick-up-in-the-store-content.html.twig
{% block pickUpInTheStore %}
{% if getCurrentPickUpInTheStore() %}
<div id="coupon-table">
<div id="pick-up-in-the-store-options-table">
<label for="pickUpInTheStore" class="radio inline">
<input type="checkbox" name="pickUpInTheStore" id="pickUpInTheStore"/>
{% trans %}Pick up in the store{% endtrans %}
</label>
</div>
</div>
{% endif %}
{% if getCurrentPickUpInTheStore() or getCurrentFindMyLocation() %}
{% block pickUpInTheStoreMap %}
<div id="pick-up-map-element" hidden>
<input type="text" class="controls" name="location-input" id="location-input">
<div id="map"></div>
<ul id="mapSidebar">
<li onclick="getLocation()" class="myLocation"><strong>{% trans %}Show My Location{% endtrans %}</strong></li>
</ul>
</div>
{% endblock pickUpInTheStoreMap %}
{% endif %}
{% endblock %}
Shipping
This template lists all available shipping options and allows user to choose one of them.
shipping.html.twig
{% extends constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/master.html.twig' %}
{% set user = get_logged_user() %}
{% block content %}
{% if alligator_session_order().shipping %}
{% set shippingId = alligator_session_order().shipping.id %}
{% endif %}
<section class="wrapper">
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/shipping-content.html.twig' %}
</section>
<script>
var shippings = [];
{% for shipping in shippingList %}
var shippingJSON = {
id: {{ shipping.id }}
};
shippings.push(shippingJSON);
{% endfor %}
$("#shipping-providers-table input[name= 'radio']").each(function (index) {
var radioBtn = new ShippingProviderRadio($(this)[0], shippings[index]);
});
{% if alligator_session_order().shipping %}
var shippingId = {{ shippingId }};
$("#shipping-providers-table input[data-shipping-id='"+shippingId+"']").attr('checked', true);
{% endif %}
$('#address-to-shipping').click(function(e){
if (!$("#shipping-providers-table input:checked").length) {
toastr.error(Translator.trans('Please select shipping provider'));
e.preventDefault();
}
});
</script>
{% endblock %}
{% block scripts %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'checkout-pages-script', 'step': 'shipping'} %}
{% endblock %}
shipping-content.html.twig
{% extends '@checkoutPluginViews/shipping-content.html.twig' %}
{% block shippingContent %}
{% block shippingCheckoutNavbar %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/checkout-navbar.html.twig' %}
{% endblock shippingCheckoutNavbar %}
{% block shippingCheckoutMain %}
<div class="table-responsive">
<table class="table table-striped" id="summary-table">
<thead>
<tr>
<th>{% trans %}Choose{% endtrans %}</th>
<th>{% trans %}Price / Rules{% endtrans %}</th>
<th>{% trans %}information{% endtrans %}</th>
</tr>
</thead>
<tbody id="shipping-providers-table">
{% set count=1 %}
{% for shipping in shippingList %}
<tr>
<td>
<label for={{ 'radio'~count }} >
<input type="radio" name="radio" data-shipping-id="{{ shipping.id }}" id="{{ 'radio'~count }}"/>
{{ shipping.name }}
</label>
</td>
<td>
{% if shipping.price and shipping.price > 0 %}
{{ shipping.price | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}
{% endif %}
</td>
<td>{{ shipping.description }}</td>
</tr>
{% set count=count+1 %}
{% endfor %}
</tbody>
</table>
</div>
{% set nextRoute = getNextStep(app.request.get('_route')) %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/apply-coupon.html.twig' %}
<div class="checkout-actions">
<a class="button-reset btn btn-danger" id="btn-back-to-shop" href="{{ path('homepage') }}">{% trans %}Back to shop{% endtrans %}</a>
<a class="btn-proceed btn btn-primary" id='address-to-shipping' href="{{ path(nextRoute) }}">{% trans %}Proceed{% endtrans %}</a>
</div>
{% endblock shippingCheckoutMain %}
{% endblock shippingContent %}
Payment
This step lists available payment options and allows user to choose the desired one.
payment.html.twig
{% extends constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/master.html.twig' %}
{% set user = get_logged_user() %}
{% block content %}
{% if alligator_session_order().paymentType %}
{% set paymentType = alligator_session_order().paymentType %}
{% endif %}
<section class="wrapper">
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/payment-content.html.twig' %}
</section>
<script>
var payments = [];
{% for paymentType in getCurrentPaymentTypes() %}
payments.push({ name: "{{ paymentType['value'] }}" });
{% endfor %}
$("#payment-options-table input[name= 'radio']").each(function (index) {
var radioBtn = new PaymentProviderRadio($(this)[0], payments[index]);
});
{% if alligator_session_order().paymentType %}
var paymentType ="{{ paymentType }}";
$("#summary-table input[data-payment-id='"+paymentType+"']").attr('checked', true);
{% endif %}
var addressToShippingFunc = function(ctx, data) {
if (data.code != 200) {
$('#modalInfoBody').text(Translator.trans(data.message));
$($('#modalReject')).modal({
show: 'false'
});
} else {
location.href = $('#address-to-shipping').attr('href');
}
};
$('#address-to-shipping').click(function(e){
if (!$("#payment-options-table input:checked").length) {
toastr.error(Translator.trans('Please select payment type'));
} else {
if (typeof coupon !== 'undefined' && $(coupon[0]).is(':checked')) {
adapter.sendData(Routing.generate('check-coupon', {_locale:'en'}, true), '', "POST", addressToShippingFunc, '');
} else {
return true;
}
}
e.preventDefault();
});
$('.payment-table input[type="radio"]').on('change', function () {
$('.funkyradio').removeClass('active');
$(this).closest('.funkyradio').addClass('active');
})
</script>
{% endblock %}
{% block scripts %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'checkout-pages-script', 'order': alligator_session_order(), 'step': 'payment'} %}
{% endblock %}
payment-content.html.twig
{% extends '@checkoutPluginViews/payment-content.html.twig' %}
{% block paymentContent %}
{% block paymentCheckoutNavbar %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/checkout-navbar.html.twig' %}
{% endblock paymentCheckoutNavbar %}
{% block couponRejectModalContent %}
{% set nextRoute = getNextStep(app.request.get('_route')) %}
<div class="modal fade" id="modalReject" tabindex="-1" role="dialog" aria-labelledby="modalExceptionLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true"><i class="fa fa-times-circle close-btn"></i>
</span>
</button>
<h3 id="modalInfo-title" class="modal-title">{% trans %}Coupon information{% endtrans %}</h3>
</div>
<div class="modal-body">
<h5>{% trans %}Your coupon will not be activated for this order.{% endtrans %}</h5>
<h5 id="modalInfoBody"></h5>
<h5>{% trans %}Your grand total for this order is:{% endtrans %}
<span id="grand-total-price-wo-coupon">
{{ order.getTotal| alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}
</span>
</h5>
<div class="modal-footer">
<a class="button-reset" id="btn-back-to-shop" href="{{ path('homepage') }}">{% trans %}BACK TO SHOP{% endtrans %}</a>
<a class="btn-proceed" href="{{ path(nextRoute) }}">{% trans %}PROCEED{% endtrans %}</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block paymentCheckoutMainContent %}
<div id="summary-table">
<p>{% trans %}Choose Payment Type{% endtrans %}</p>
<div id="payment-options-table">
{% for key,paymentType in getCurrentPaymentTypes() %}
<label for="radio{{ loop.index }}">
<input type="radio" name="radio" data-payment-id="{{ paymentType['id'] }}" id="radio{{ loop.index }}"/>
{{ key }}
</label>
{% endfor %}
</div>
</div>
{% if app.session.flashBag.has('error') %}
<div>
{% for msg in app.session.flashBag.get('error') %}
{{ msg }}
{% endfor %}
</div>
{% endif %}
{% set nextRoute = getNextStep(app.request.get('_route')) %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/apply-coupon.html.twig' %}
<div class="checkout-actions">
<a class="button-reset btn btn-danger" id="btn-back-to-shop" href="{{ path('homepage') }}">{% trans %}Back to shop{% endtrans %}</a>
<a class="btn-proceed btn btn-primary" id='address-to-shipping' href="{{ path(nextRoute) }}">{% trans %}Proceed{% endtrans %}</a>
</div>
{% endblock paymentCheckoutMainContent %}
{% endblock %}
This template activates coupon if user has one.
apply-coupon.html.twig
{% if nextRoute == 'checkout_success' %}
{% if app.session.get('COUPON_PK') %}
{% block couponApplyContent %}
<div id="coupon-table" class="form-group">
<div id="coupon-options-table">
<label for="radioCouponActivate">
<input type="checkbox" name="radioCoupon" data-coupon-id="coupon" id="radioCouponActivate"/>
{% trans %}Activate Coupon{% endtrans %}
</label>
</div>
</div>
{% endblock %}
{% block couponApplyScript %}
<script>
var coupon = $("#coupon-options-table input[name = 'radioCoupon']");
var couponActivated = false;
{% if app.session.get('COUPON_ACTIVATED_PK')|slice(2, 1) %}
$(coupon).attr('checked', true);
couponActivated = true;
{% endif %}
var activateCouponButton = new CouponActivateRadio($(coupon)[0], 'Coupon');
</script>
{% endblock %}
{% endif %}
{% endif %}
Success
This is the final step which ends the order. User gets the message that the order process went successfully and an option to leave a comment.
success.html.twig
{% extends constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/master.html.twig' %}
{% set user = get_logged_user() %}
{% block content %}
<section class="wrapper">
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/success-content.html.twig' %}
</section>
{% endblock %}
{% block scripts %}
<script>
$("#send_comment_order").on("click", function() {
$("#send_comment_order").prop("disabled", true);
var orderObj = {
checkout_comment: $("#order_comment").val(),
id:{{ orderId|json_encode|raw }}
};
function successFunction (ctx, data) {
if (data.code === 200) {
toastr.success(Translator.trans("Thank you! You will be notified."));
$("#order_comment").val("");
setTimeout(function(){
window.location = Routing.generate('homepage', {_locale: getURLParameter('locale')}, true);
}, 2000);
} else {
toastr.success(Translator.trans(data.message));
$("#send_comment_order").prop("disabled", false);
}
}
adapter.sendData(Routing.generate('add_checkout_comment_json', {_locale: getURLParameter('locale')}, true),orderObj, "POST", successFunction, "");
});
</script>
{{ alligator_get_plugins_for_layout('success-pages-script', {'orderId': orderId|json_encode|raw(), 'token': token}) }}
{% endblock %}
success-content.html.twig
{% block successContent %}
{% block successCheckoutNavbar %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/checkout-navbar.html.twig' %}
{% endblock successCheckoutNavbar %}
{% block successMainContent %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'checkout-success-content'} %}
<div class="wrap">
<div class="col-m-6 col-m-ofs-3">
<h4>{% trans %}Successful Order{% endtrans %}!</h4>
{% if orderData is defined and orderData is not null %}
<table id="card-payment-information" class="table table-striped">
<tbody>
<tr>
<th>{% trans %}Status{% endtrans %}</th>
<td> {{ orderData.code }} {{ orderData.description }}</td>
</tr>
<tr>
<th>{% trans %}Order number{% endtrans %}</th>
<td>{{ orderData.merchantTransactionId }}</td>
</tr>
<tr>
<th>{% trans %}Currency and value{% endtrans %}</th>
<td>{{ orderData.amount }} {{ orderData.currency }}</td>
</tr>
<tr>
<th>{% trans %}Card type{% endtrans %}</th>
<td>{{ orderData.paymentBrand }}</td>
</tr>
<tr>
<th>{% trans %}Bank authorisation code{% endtrans %}</th>
<td>{{ orderData.bankAuthCode }}</td>
</tr>
<tr>
<th>{% trans %}Timestamp{% endtrans %}</th>
<td>{{ orderData.timestamp }}</td>
</tr>
</tbody>
</table>
{% endif %}
<p>{% trans %}Your order has been successfully received. Please leave a comment!{% endtrans %}</p>
<textarea name="order_comment" class="form-control" id="order_comment"></textarea>
<button id="send_comment_order" class="btn btn-primary">{% trans %}Send Comment{% endtrans %}</button>
<a class="btn btn-success" id="btn-back-to-shop" href="{{ path('homepage') }}">{% trans %}Back to shop{% endtrans %}</a>
</div>
</div>
{% endblock successMainContent %}
{% endblock %}
One-step checkout
One-step checkout is a type of checkout where everything necessary is on one page. Checkout steps in the Dashboard is the place where user chooses the type of checkout. The parent twig is loading child templates and scripts.
one-step-checkout.html.twig
{% extends constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Layout/master.html.twig' %}
{% set user = get_logged_user() %}
{% set userSettings = get_current_user_settings() %}
{% set addressStepActive = ('address' in get_active_checkout_steps()) %}
{% set shippingStepActive = ('shipping' in get_active_checkout_steps()) %}
{% set paymentStepActive = ('payment' in get_active_checkout_steps()) %}
{% block content %}
{% if user %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/one-step-checkout-edit-address-modal.html.twig' %}
{% endif %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/one-step-checkout-content.html.twig' %}
{% endblock content %}
{% block scripts %}
<script src="{{ asset('Resource/Plugins/EmpireCheckoutPlugin/js/pick_up_in_the_store_scripts_compiled.js') }}"></script>
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/one-step-checkout-scripts.html.twig' %}
{% endblock scripts %}
This is the main child template. Everything is placed in it.
Functions that are used:
- getCurrentSettingsObject: gets the user settings object frtom the database
one-step-checkout-content.html.twig
{% set addressStepActive = ('address' in get_active_checkout_steps()) %}
{% set shippingStepActive = ('shipping' in get_active_checkout_steps()) %}
{% set paymentStepActive = ('payment' in get_active_checkout_steps()) %}
{% set pickUpInTheStoreActive = getCurrentPickUpInTheStore() %}
{% set findMyLocationActive = getCurrentFindMyLocation() %}
{% set currentSettingsObject = getCurrentSettingsObject() %}
{% if not order is same as(false) and order.items|length %}
<div class="{{ addressStepActive ? pickUpInTheStoreActive or findMyLocationActive ? 'col-m-12' : 'col-xs-12' : 'col-m-12' }}">
{% if not (not addressStepActive and user) %}
<h1>{% trans %}Billing Information{% endtrans %}</h1>
{% endif %}
{% if not user %}
{% if addressStepActive %}
<h5>{% trans %}Login as Guest (or) Register{% endtrans %}</h5>
<label for="oneStepGuest">
<input type="radio" id="oneStepGuest" name="typeOfUser">
{% trans %}Guest{% endtrans %}
</label>
<label for="oneStepRegister" class="radio inline">
<input type="radio" id="oneStepRegister" name="typeOfUser" checked>
{% trans %}Register{% endtrans %}
</label>
{#ADDED SETTINGS IN BACKEND#}
{% if userSettings.isB2BRegistration is null %}
<div id="account-type-toggle">
<h5>{% trans %}Account type{% endtrans %}</h5>
<input type="hidden" name="type" class="form-control" id="type" value="PERSONAL">
<label for="personalButton">
<input type="radio" checked name="account-type" class="personal company-personal-btn active" id="personalButton">
{% trans %}Personal{% endtrans %}
</label>
<label for="companyButton">
<input type="radio" name="account-type" class="company company-personal-btn" id="companyButton">
{% trans %}Company{% endtrans %}
</label>
</div>
{% endif %}
{% endif %}
</div>
<h5>{% trans %}LOG IN{% endtrans %}</h5>
<form role="form" action="{{ path('fos_user_security_check') }}" method="post" id="login-form" class="clearfix">
<input type="hidden" name="_csrf_token" value="{{ fos_csrf_provider.getToken('authenticate').getValue }}" />
<div class="form-group">
<label for="username">{{ 'security.login.username'|trans }}</label>
<input type="text" id="username" name="_username" class="form-control">
</div>
<div class="form-group">
<label for="password">{{ 'security.login.password'|trans }}</label>
<input type="password" id="password" name="_password" class="form-control">
</div>
<input type="text" value="{{ path('checkout_summary', {'_locale':locale}) }}" name="_route" hidden>
<label for="remember-me">
<input id="remember-me" type="checkbox" name="checkbox"/>
{{ 'security.login.remember_me'|trans }}
</label>
<a href="{{ path('fos_user_resetting_request') }}" id="forgot-pass">{{ 'Forgot password?'|trans }}</a>
<button type="submit" class="btn btn-primary" id="_submit" name="_submit">{{ 'security.login.submit'|trans }}</button>
</form>
<div class="form-group">
<label for="firstName">{% trans %}First name{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="firstName">
</div>
<div class="form-group">
<label for="lastName">{% trans %}Last name{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="lastName">
</div>
<div class="form-group">
<label for="userEmail">{% trans %}Email{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="userEmail">
</div>
{% if userSettings.isB2BRegistration in [null, true] %}
<div class="form-group company-only {{ userSettings.isB2BRegistration is null ? 'hidden' }}">
<label for="companyName">{% trans %}Company Name{% endtrans %}:</label>
<input type="text" name="companyName" class="form-control" id="companyName">
</div>
<div class="form-group company-only {{ userSettings.isB2BRegistration is null ? 'hidden' }}">
<label for="pib">{% trans %}PIB{% endtrans %}:</label>
<input type="text" name="pib" class="form-control" id="pib">
</div>
{% endif %}
<div id="registerContent" class="clearfix">
<div class="form-group">
<label for="userName">{% trans %}Username{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="userName">
</div>
<div class="form-group">
<label for="userEmail">{% trans %}Shipping email{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="userEmail">
</div>
<div class="form-group">
<label for="phoneNumber">{% trans %}Phone{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="phoneNumber">
</div>
<div class="form-group">
<label for="mobilePhone">{% trans %}Mobile phone{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="mobilePhone">
</div>
<div class="form-group">
<label for="userPassword">{% trans %}Password{% endtrans %}</label>
<input type="password" placeholder="" class="form-control" id="userPassword">
</div>
<div class="form-group">
<label for="userPasswordConfirm">{% trans %}Repeat password{% endtrans %}</label>
<input type="password" placeholder="" class="form-control" id="userPasswordConfirm">
</div>
<div class="form-group">
<label for="userAutocompleteShipping">{% trans %}Address{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="userAutocompleteShipping">
</div>
<div class="form-group">
<label for="route">{% trans %}Street{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="route" name="route" disabled>
</div>
<div class="form-group">
<label for="street_number">{% trans %}Street number{% endtrans %}</label>
<input type="text" placeholder="" id="street_number" name="street_number" class="form-control" disabled>
</div>
<div class="form-group">
<label for="locality">{% trans %}City{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="locality" name="locality" disabled>
</div>
<div class="form-group">
<label for="locality">{% trans %}Country{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="locality" name="country" disabled>
</div>
<div class="form-group">
<label for="postal_code">{% trans %}Zip code{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="postal_code" name="postal_code" disabled>
</div>
</div>
{% if addressStepActive %}
<h1>{% trans %}Shipping Address{% endtrans %}</h1>
<div class="shipToAddress">
<label for="shipToSameAddress" style="display: none;">
<input type="checkbox" id="shipToSameAddress" checked>
{% trans %}Ship to same address{% endtrans %}
</label>
<label for="newShippingAddressAdd" style="display: none;">
<input type="checkbox" id="newShippingAddressAdd">
{% trans %}Add new address{% endtrans %}
</label>
</div>
{% endif %}
{% endif %}
{% if user and addressStepActive %}
<h2>{% trans %}Select one of your addresses{% endtrans %}</h2>
<div class="form-group" id="shipping-addresses-container-dnt">
{% set selected = false %}
{% for address in user.getShippingAddresses %}
{% if order is defined and order.address is not null and address is not null %}
{% if order.address.id == address.id %}
{% set selected = true %}
{% set selectedAddressId = address.id %}
<div class="shipping-address-holder-dnt one-step-address" data-id="{{ address.id }}">
<p class="select-trigger-dnt selected"><span>{{ address.street }} {{ address.streetNumber }}, {{ address.postcode }} {{ address.city }} {{ address.country }}</span></p>
<i class="fa fa-close remove-address-dnt"></i>
<i class="fa fa-pencil edit-address-dnt"></i>
</div>
{% else %}
<div class="shipping-address-holder-dnt one-step-address" data-id="{{ address.id }}">
<p class="select-trigger-dnt"><span>{{ address.street }} {{ address.streetNumber }}, {{ address.postcode }} {{ address.city }} {{ address.country }}</span></p>
<i class="fa fa-close remove-address-dnt"></i>
<i class="fa fa-pencil edit-address-dnt"></i>
</div>
{% endif %}
{% else %}
<div class="shipping-address-holder-dnt one-step-address" data-id="{{ address.id }}"><p class="select-trigger-dnt"><span>{{ address.street }} {{ address.streetNumber }}, {{ address.postcode }} {{ address.city }} {{ address.country }}</span></p>
<i class="fa fa-close remove-address-dnt"></i>
<i class="fa fa-pencil edit-address-dnt"></i>
</div>
{% endif %}
{% endfor %}
<div class="shipping-address-holder-dnt one-step-address" data-id="{{ user.address.id }}">
<p class="select-trigger-dnt {% if not selected %}selected {% endif %}"><span> {{ user.address.street }} {{ user.address.streetNumber }}, {{ user.address.postcode }} {{ user.address.city }} {{ user.address.country }}</span></p>
<i class="fa fa-close remove-address-dnt"></i>
<i class="fa fa-pencil edit-address-dnt"></i>
{% set selected = true %}
</div>
</div>
<div class="form-group">
<label for="newShippingAddressAdd">
<input type="checkbox" id="newShippingAddressAdd">
{% trans %}Add new address{% endtrans %}
</label>
</div>
{% endif %}
{% if addressStepActive %}
{% if shouldShowShipToOtherAddress() %}
<div class="form-group">
<label for="shipToOtherAddress">
<input type="checkbox" id="shipToOtherAddress">
{% trans %}Ship to other address{% endtrans %}
</label>
</div>
{% endif %}
<div id="shippingAddressSelection">
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/find-my-location-button.html.twig' %}
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/pick-up-in-the-store-content.html.twig' %}
</div>
<div id="shippingAddressContent" hidden>
{% if shouldShowShipToOtherAddress() %}
<div class="form-group shipToOtherDependent">
<label for="shipToFirstName">{% trans %}First Name{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="shipToFirstName">
</div>
<div class="form-group shipToOtherDependent">
<label for="shipToLastName">{% trans %}Last name{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="shipToLastName">
</div>
{% endif %}
<div class="form-group">
<label for="email">{% trans %}Email address{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="email">
</div>
<div class="form-group">
<label for="autocompleteShipping">{% trans %}Shipping address{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="autocompleteShipping">
</div>
<div class="form-group">
<label for="route_shipping">{% trans %}Street{% endtrans %}</label>
<input type="text" placeholder="" id="route_shipping" name="route_shipping" class="form-control" disabled>
</div>
<div class="form-group">
<label for="street_number_shipping">{% trans %}Street number{% endtrans %}</label>
<input type="text" placeholder="" id="street_number_shipping" name="street_number_shipping" class="form-control" disabled>
</div>
<div class="form-group">
<label for="locality_shipping">{% trans %}City{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="locality_shipping" name="locality_shipping" disabled>
</div>
<div class="form-group">
<label for="country_shipping">{% trans %}Country{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="country_shipping" name="country_shipping" disabled>
</div>
<div class="form-group">
<label for="postal_code_shipping">{% trans %}Zip code{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="postal_code_shipping" name="postal_code_shipping" disabled>
</div>
<div class="form-group">
<label for="phoneNumberFieldShipping">{% trans %}Phone number{% endtrans %}</label>
<input type="text" placeholder="" class="form-control" id="phoneNumberFieldShipping" name="phone_number_shipping">
</div>
</div>
{% endif %}
<div class="{{ addressStepActive ? pickUpInTheStoreActive or findMyLocationActive ? 'col-m-12' : 'col-xs-12' : 'col-m-12' }}">
{% if shippingStepActive or paymentStepActive %}
{% if shippingStepActive %}
<div id="shipping-select-container">
<h1>{% trans %}Shipping Methods{% endtrans %}</h1>
{% for shipping in shippingList %}
<label for="shippingProvider">
<input type="radio" name="shippingProvider" value="{{ shipping.id }}" {{ app.user and user.defaultShippingProvider and shipping.id == user.defaultShippingProvider.id ? 'checked' }}>
{{ shipping.name|trans }}
</label>
{% endfor %}
</div>
{% endif %}
{% if paymentStepActive %}
<h1>{% trans %}Payment Methods{% endtrans %}</h1>
{% set selectedPaymentType = order.paymentType ? order.paymentType : app.user ? user.defaultPaymentType : null %}
{% for key, payment in getCurrentPaymentTypes(order) %}
<div>
<label for="paymentType">
<input type="radio" name="paymentType" value="{{ key }}" {{ key == selectedPaymentType ? 'checked' }}>
{{ key|trans }}
</label>
</div>
{% endfor %}
{% endif %}
{% endif %}
<h1>{% trans %}Order Review{% endtrans %}</h1>
<div id="checkout-summary-table-data">
{% include constant('Plugins\\EmpirePluginsBundle\\Routing\\RouteThemeListener::SESSION_THEME_NAME') ~ '/Plugins/EmpireCheckoutPlugin/views/one-step-checkout-summary-table.html.twig' %}
</div>
<label for="commentArea">{% trans %}Comment{% endtrans %}</label>
<textarea cols="5" rows="5" class="form-control" id="commentArea"></textarea>
<div class="{{ user ? 'to-right' }}">
{% if not user %}
<label for="one_step_terms_and_conditions">
<input type="checkbox" name="checkbox" id="one_step_terms_and_conditions"/>
<span>{{ 'I accept term and conditions'|trans }}</span>
</label>
{% endif %}
<button class="btn btn-primary" id="place-order-btn">{% trans %}Place order{% endtrans %}</button>
</div>
{% else %}
<div class="alert alert-danger">
{% trans %}Your cart is empty! Please add products to your cart first{% endtrans %}
</div>
{% endif %}
</div>
one-step-checkout-summary-table.html.twig
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>{% trans %}Product image{% endtrans %}</th>
<th>{% trans %}Product name{% endtrans %}</th>
<th>{% trans %}State{% endtrans %}</th>
<th>{% trans %}Price with vat{% endtrans %}</th>
<th class="discount-row">{% trans %}Discount{% endtrans %}</th>
<th>{% trans %}Quantity{% endtrans %}</th>
<th>{% trans %}Subtotal with vat{% endtrans %}</th>
</tr>
</thead>
<tbody>
{% set sumOfAllAdjustmentsTotal = 0 %}
{% for orderItem in order.items %}
{% set quantityStep = 1 %}
<tr id="item-{{ orderItem.id }}">
<td>
<a href="{{ path('product_front_slug', {'_locale': app.request.locale, 'slug': orderItem.product.slug}) }}">
{% if orderItem.product.baseImageUrl %}
<img src="{{ getExtensionBySize(orderItem.product.baseImageUrl, "Only Resize" ) }}" alt="">
{% else %}
<img src="{{ asset('Resource/FrontendTheme/images/default_product.png') }}" alt="">
{% endif %}
</a>
</td>
<td>
<a href="{{ path('product_front_slug', {'_locale': app.request.locale, 'slug': orderItem.product.slug}) }}">
{{ orderItem.product.getProductName }}
</a>
</td>
<td>
{% set informationQuantity = orderItem.productAttValues.count ? orderItem.productAttValues.get(0).quantity : orderItem.product.simpleProduct.numberInStock %}
{% if can_see_quantity_light(orderItem.product.permissions) %}
{% set simpleProduct = orderItem.product.simpleProduct %}
{% set flagStatus = 'red' %}
{% set infoText = 'not in stock' %}
{% if informationQuantity > simpleProduct.maxQuantityLightValue %}
{% set flagStatus = 'green' %}
{% set infoText = 'in stock' %}
{% elseif informationQuantity > simpleProduct.minQuantityLightValue %}
{% set flagStatus = 'yellow' %}
{% set infoText = 'low stock' %}
{% endif %}
<span class=""><i class="fa fa-flag {{ flagStatus }}"></i></span>
{% endif %}
</td>
<td>{{ orderItem.getUnitPrice | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</td>
<td class="discount-row">
{% set sumOfAllAdjustmentsTotal = sumOfAllAdjustmentsTotal + ((orderItem.adjustmentsTotal | abs) / 100 * orderItem.getUnitPrice * orderItem.quantity) %}
{{ (orderItem.adjustmentsTotal | abs) }} {{ orderItem.adjustmentsTotal ? '%' }}
</td>
<td>
<button class="btn-quantity" data-dir="dwn" data-step="{{ quantityStep }}" data-action-type="decrease">
<i class="fa fa-minus"></i>
</button>
<input type="text" class="form-control text-center quantity-input" min="1" value="{{ orderItem.quantity }}">
<button class="btn-quantity" data-dir="up" data-step="{{ quantityStep }}" data-action-type="increase">
<i class="fa fa-plus"></i>
</button>
<span class="tooltip-quantity"></span>
</td>
<td>{{ orderItem.total | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</td>
<td><i class="fa fa-close"></i></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% block summaryStepAfterTableLayout %}
{% include '@pluginBundleViews/Layout/include-template-by-layout.html.twig' with {'layout_name': 'one-step-checkout-after-table'} %}
{% endblock summaryStepAfterTableLayout %}
<div class="table-responsive">
<table class="table table-striped">
<tbody>
<tr>
<th>{% trans %}Grand Total{% endtrans %}</th>
<th colspan="2">{{ (order.getTotalWithTax + sumOfAllAdjustmentsTotal) | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</th>
</tr>
<tr>
<th>{% trans %}Discount{% endtrans %}</th>
<th colspan="2">{{ sumOfAllAdjustmentsTotal | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</th>
</tr>
<tr>
<th>{% trans %}Grand Total Price{% endtrans %}</th>
<th colspan="2">{{ order.getTotalWithTax | alligator_json_convert(app.session.get('current_currency')) | alligator_money_format }}</th>
</tr>
</tbody>
</table>
</div>
<script>
$('.image-tooltip').tooltip();
var orderItems = [
{% for item in order.items if item.product != null%}
{
unit_price: {{ item.unitPrice ? item.unitPrice : '0'}},
product: {
id: {{ item.product.id }},
simple_product: {
number_in_stock: {{ item.product.simpleProduct.numberInStock }}
},
quantity_step: {{ item.product.quantityStep ? item.product.quantityStep : 1 }}
},
quantity: {{ item.quantity }},
product_att_values: [
{% for value in item.productAttValues %}
{
id: {{ value.id }},
cost: {{ value.cost ? value.cost : '0' }},
sku: '{{ value.sku }}',
quantity: {{ value.quantity }},
min_quantity: {{ value.minQuantity }},
quantity_step: {{ value.quantityStep }},
display_to_user: {{ value.displayToUser ? "true" : "false" }},
price_on_action: {{ value.priceOnAction ? value.priceOnAction : "null" }},
attribute_values: [
{% for attr in value.attributeValues %}
{
id: {{ attr.id }}
}
{% if not loop.last %},{% endif %}
{% endfor %}
]
}
{% if not loop.last %},{% endif %}
{% endfor %}
]
},
{% endfor %}
];
// insertIntoLocalStorage("order", currentOrder);
$('input.quantity-input').each(function (index) {
var qtyInput = new QtyInput($(this)[0], orderItems[index]);
});
$('.delete-btn').each(function(index, element){
var removeBtn = new RemoveButton(element, orderItems[index]);
});
</script>
one-step-checkout-edit-address-modal.html.twig
{% block editAddressModal %}
<div class="modal fade" id="shippingAddressModalEdit" tabindex="-1" role="dialog"
aria-labelledby="shippingAddressModalEdit" data-address-id="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="myModalLabel">{% trans %}Edit shipping address{% endtrans %} <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">
<i class="fa fa-close"></i>
</span></button></h4>
</div>
<div class="modal-body">
<form role="form" id="address-form">
<div class="form-group">
<label>{% trans %}Email address{% endtrans %}</label>
<input type="text" class="form-control" name="emailEdit" id="emailEdit" autocomplete="off">
</div>
<div class="form-group">
<label>{% trans %}Please enter your billing address below{% endtrans %}</label>
<input type="text" class="form-control" name="address" id="autocompleteEdit"
onfocus="geolocateEdit()" autocomplete="off"
placeholder="{% trans %}Enter a location{% endtrans %}">
<label for="autocomplete"></label>
</div>
<div class="form-group">
<label for="route">{% trans %}Street {% endtrans %}</label>
<input type="text" name="street" class="form-control" id="route_edit">
</div>
<div class="form-group">
<label for="street_number">{% trans %}Street number{% endtrans %}</label>
<input type="text" name="street_number_order" class="form-control" id="street_number_edit">
</div>
<div class="form-group">
<label for="country">{% trans %}Country{% endtrans %}</label>
<input type="text" id="country_edit" class="form-control" name="country_edit" required>
</div>
<div class="form-group">
<label for="locality">{% trans %}City{% endtrans %}</label>
<input type="text" name="city" class="form-control" id="locality_edit">
</div>
<div class="form-group ">
<label for="postal_code">{% trans %}ZIP{% endtrans %}</label>
<input type="text" name="postcode_order" class="form-control"
id="postal_code_edit">
</div>
<div class="form-group">
<label for="phone_number">{% trans %}Phone number{% endtrans %}</label>
<input type="text" name="phone_number_edit" class="form-control" id="phone_number_edit">
</div>
<div class="form-group">
<a class="btn btn-primary" id='address-edit' href="#">{% trans %}Save{% endtrans %}</a>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock editAddressModal %}
There, we have covered all of the required Starter theme twig templates. This skeleton theme is open for customization and further development.
SASS - How To
In this part of the documentation, you can find out how our SASS framework works and how developers can use it and customize it according to their needs.
This is files and folders structure:
### Folder structure
theme-sass/
|__base/
| |__ _grid.scss
| |__ _fonts.scss
| |__ _print.scss
| |__ _reboot.scss
| |__ _root.scss
| |__ _type.scss
|
|__mixins/
| |__ _box_shadow.scss
| |__ _breakpoints.scss
| |__ _caret.scss
| |__ _clearfix.scss
| |__ _display.scss
| |.. ...
|
|__utils/
| |__ _functions.scss
| |.. ...
|
|_vars/
| |__ _colors.scss
| |__ _grid.scss
| |__ _options.scss
| |__ _type.scss
| |.. ...
|
|__vendor/
| |.. ...
|
|__sandbox/
| |.. ...
|__ _base.scss
|__ _mixins.scss
|__ _vars.scss
|__ _vendor.scss
|__ style.scss
- Base should include global styles settings, grid, print styles, typography etc...
- Mixins should have all mixins inside (for creating grid, custom helpful mixins etc...)
- Utils utility folder - typically only functions and some helpful sass mixins used for testing/developing and less in project
- Vars all variables across site (splitted in as much subfolders as makes sense)
- Vendor contain all SCSS files from external libraries and frameworks.
- Sandbox not to be used directly in production, it is place to test/storage some scss that will/might be useful
Formatting
Indentation and spacing
- Be consistent with indentation. On F-Webshop projects, indentation is 1 tab
- Use a single space after a property's name colon
- Use one whitespace between an element and a bracket
- Use one selector per line, one rule per line
.button,
.link {
color: $white;
text-decoration: none;
}
- Use Line-Break between styles, new element and after closing the bracket
.button {
background-color: $red;
&.button-large {
width: 100%;
}
&:hover {
background-color: $black;
}
@media #{$md-down} {
font-size: 0.8em;
}
}
.link {
color: $blue;
}
- No space before and after single line comment that wraps styles section
// Button - START
.button {
background-color: $black;
}
// Button - END
- Comments that are in the same line as code are perceded by one space
.button-move {
cursor: pointer; // Use as falback
cursor: grab;
}
- Commas in function arguments should be followed by a single space
.button {
box-shadow: 0 1px 1px 2px rgba($black, .2);
transition: all 2.5s cubic-bezier(1, 0, 0, 1);
}
Naming
- Use hyphens rather than underscores and camelCase in class names
// Bad example
.button_outline {
...
}
.buttonOutline {
...
}
// Good example
.button-outline {
...
}
- Use hyphens rather than underscores and camelCase in variable and mixin names
- Do not use ID selectors
- Use a tag as a root selector only in base declaration partial
- Use meaningful or generic class names
- Use class names that are as short as possible but as long as necessary
// Not recommended
.navigation {}
.athr {}
// Recommended
.nav {}
.author {}
Nesting and Ordering
- Selectors should be nested in the following way: pseudo-elements, pseudo-selectors, @inlcude blocks, child selectors. Order can be changed if certain child selectors are affected by parent hover, for example.
.button {
@include hide-text;
width: 100px;
color: $white;
background-color: $facebook;
&::before {
...
}
&::after {
...
}
&:hover {
...
}
&:focus {
...
}
@media #{$md-up} {
width: 100%;
}
.icon-facebook {
font-size: 0.9em;
}
}
- Nesting depth should not be greater than 5
- Selector depth should not be greater than 3
Style Rules
- Do not use units after 0 values unless they are required
// Bad example
.button {
box-shadow: 0px 0px 1px $black;
margin: 0px;
}
// Good example
.button {
box-shadow: 0 0 1px $black;
margin: 0;
}
- Always use hex notation for color variables
// Bad example
$black: black;
$white: rgb(255, 255, 255);
$red: hsl(0, 100%, 50%);
// Good example
$black: #000;
$white: #fff;
$red: #f00;
- Use color variables as function arguments
background-color: rgba($black, .2);
color: lighten($black, 80%);
- Always use single quotes
&::before {
content: '';
}
- Never use vendor prefixes
Comments
- Always use single line comments style
.button {
@inlcude hide-text; // Text for icon button needs to be hidden
}
// This
// is
// the
// multiline
// comment
- Use 'temp style' comment in uppercase with your name alongside
.button {
color: $red !important; // TEMP - John Doe
}